diff options
Diffstat (limited to 'ml/dlib/examples/thread_pool_ex.cpp')
-rw-r--r-- | ml/dlib/examples/thread_pool_ex.cpp | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/ml/dlib/examples/thread_pool_ex.cpp b/ml/dlib/examples/thread_pool_ex.cpp new file mode 100644 index 00000000..e0a566ef --- /dev/null +++ b/ml/dlib/examples/thread_pool_ex.cpp @@ -0,0 +1,183 @@ +// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt +/* + + This is an example illustrating the use of the thread_pool + object from the dlib C++ Library. + + + In this example we will crate a thread pool with 3 threads and then show a + few different ways to send tasks to the pool. +*/ + + +#include <dlib/threads.h> +#include <dlib/misc_api.h> // for dlib::sleep +#include <dlib/logger.h> +#include <vector> + +using namespace dlib; + +// We will be using the dlib logger object to print messages in this example +// because its output is timestamped and labeled with the thread that the log +// message came from. This will make it easier to see what is going on in this +// example. Here we make an instance of the logger. See the logger +// documentation and examples for detailed information regarding its use. +logger dlog("main"); + + +// Here we make an instance of the thread pool object. You could also use the +// global dlib::default_thread_pool(), which automatically selects the number of +// threads based on your hardware. But here let's make our own. +thread_pool tp(3); + +// ---------------------------------------------------------------------------------------- + +class test +{ + /* + The thread_pool accepts "tasks" from the user and schedules them for + execution in one of its threads when one becomes available. Each task + is just a request to call a function. So here we create a class called + test with a few member functions, which we will have the thread pool call + as tasks. + */ +public: + + void mytask() + { + dlog << LINFO << "mytask start"; + + dlib::future<int> var; + + var = 1; + + // Here we ask the thread pool to call this->subtask() and this->subtask2(). + // Note that calls to add_task() will return immediately if there is an + // available thread. However, if there isn't a thread ready then + // add_task() blocks until there is such a thread. Also, note that if + // mytask() is executed within the thread pool then calls to add_task() + // will execute the requested task within the calling thread in cases + // where the thread pool is full. This means it is always safe to spawn + // subtasks from within another task, which is what we are doing here. + tp.add_task(*this,&test::subtask,var); // schedule call to this->subtask(var) + tp.add_task(*this,&test::subtask2); // schedule call to this->subtask2() + + // Since var is a future, this line will wait for the test::subtask task to + // finish before allowing us to access the contents of var. Then var will + // return the integer it contains. In this case result will be assigned + // the value 2 since var was incremented by subtask(). + int result = var; + dlog << LINFO << "var = " << result; + + // Wait for all the tasks we have started to finish. Note that + // wait_for_all_tasks() only waits for tasks which were started by the + // calling thread. So you don't have to worry about other unrelated + // parts of your application interfering. In this case it just waits + // for subtask2() to finish. + tp.wait_for_all_tasks(); + + dlog << LINFO << "mytask end" ; + } + + void subtask(int& a) + { + dlib::sleep(200); + a = a + 1; + dlog << LINFO << "subtask end "; + } + + void subtask2() + { + dlib::sleep(300); + dlog << LINFO << "subtask2 end "; + } + +}; + +// ---------------------------------------------------------------------------------------- + +int main() try +{ + // tell the logger to print out everything + dlog.set_level(LALL); + + + dlog << LINFO << "schedule a few tasks"; + + test taskobj; + // Schedule the thread pool to call taskobj.mytask(). Note that all forms of + // add_task() pass in the task object by reference. This means you must make sure, + // in this case, that taskobj isn't destructed until after the task has finished + // executing. + tp.add_task(taskobj, &test::mytask); + + // This behavior of add_task() enables it to guarantee that no memory allocations + // occur after the thread_pool has been constructed, so long as the user doesn't + // call any of the add_task_by_value() routines. The future object also doesn't + // perform any memory allocations or contain any system resources such as mutex + // objects. If you don't care about memory allocations then you will likely find + // the add_task_by_value() interface more convenient to use, which is shown below. + + + + // If we call add_task_by_value() we pass task objects to a thread pool by value. + // So in this case we don't have to worry about keeping our own instance of the + // task. Here we create a lambda function and pass it right in and everything + // works like it should. + dlib::future<int> num = 3; + tp.add_task_by_value([](int& val){val += 7;}, num); // adds 7 to num + int result = num.get(); + dlog << LINFO << "result = " << result; // prints result = 10 + + + // dlib also contains dlib::async(), which is essentially identical to std::async() + // except that it launches tasks to a dlib::thread_pool (using add_task_by_value) + // rather than starting an unbounded number of threads. As an example, here we + // make 10 different tasks, each assigns a different value into the elements of the + // vector vect. + std::vector<std::future<unsigned long>> vect(10); + for (unsigned long i = 0; i < vect.size(); ++i) + vect[i] = dlib::async(tp, [i]() { return i*i; }); + // Print the results + for (unsigned long i = 0; i < vect.size(); ++i) + dlog << LINFO << "vect["<<i<<"]: " << vect[i].get(); + + + // Finally, it's usually a good idea to wait for all your tasks to complete. + // Moreover, if any of your tasks threw an exception then waiting for the tasks + // will rethrow the exception in the calling context, allowing you to handle it in + // your local thread. Also, if you don't wait for the tasks and there is an + // exception and you allow the thread pool to be destructed your program will be + // terminated. So don't ignore exceptions :) + tp.wait_for_all_tasks(); + + + /* A possible run of this program might produce the following output (the first + column is the time the log message occurred and the value in [] is the thread + id for the thread that generated the log message): + + 0 INFO [0] main: schedule a few tasks + 0 INFO [1] main: task start + 0 INFO [0] main: result = 10 + 200 INFO [2] main: subtask end + 200 INFO [1] main: var = 2 + 200 INFO [0] main: vect[0]: 0 + 200 INFO [0] main: vect[1]: 1 + 200 INFO [0] main: vect[2]: 4 + 200 INFO [0] main: vect[3]: 9 + 200 INFO [0] main: vect[4]: 16 + 200 INFO [0] main: vect[5]: 25 + 200 INFO [0] main: vect[6]: 36 + 200 INFO [0] main: vect[7]: 49 + 200 INFO [0] main: vect[8]: 64 + 200 INFO [0] main: vect[9]: 81 + 300 INFO [3] main: subtask2 end + 300 INFO [1] main: task end + */ +} +catch(std::exception& e) +{ + std::cout << e.what() << std::endl; +} + + |