thread_pool_ex.cpp 7.01 KB
Newer Older
1
// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt
Davis King's avatar
Davis King committed
2
3
4
5
6
7
/*

    This is an example illustrating the use of the thread_pool 
    object from the dlib C++ Library.


8
9
    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.
Davis King's avatar
Davis King committed
10
11
12
*/


13
14
15
#include <dlib/threads.h>
#include <dlib/misc_api.h>  // for dlib::sleep
#include <dlib/logger.h>
16
#include <vector>
Davis King's avatar
Davis King committed
17
18
19

using namespace dlib;

20
// We will be using the dlib logger object to print messages in this example
Davis King's avatar
Davis King committed
21
// because its output is timestamped and labeled with the thread that the log
22
23
// 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
Davis King's avatar
Davis King committed
24
25
26
// documentation and examples for detailed information regarding its use.
logger dlog("main");

27

28
29
30
// 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.
31
32
thread_pool tp(3);

Davis King's avatar
Davis King committed
33
34
35
36
37
// ----------------------------------------------------------------------------------------

class test
{
    /*
38
39
40
        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 
41
        test with a few member functions, which we will have the thread pool call 
42
        as tasks.
Davis King's avatar
Davis King committed
43
44
45
    */
public:

46
    void mytask()
Davis King's avatar
Davis King committed
47
    {
48
        dlog << LINFO << "mytask start";
Davis King's avatar
Davis King committed
49

50
        dlib::future<int> var;
51
52
53
54
55

        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 
56
57
58
59
60
61
        // 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.
62
63
64
65
        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 
Davis King's avatar
Davis King committed
66
        // finish before allowing us to access the contents of var.  Then var will 
67
        // return the integer it contains.  In this case result will be assigned 
Davis King's avatar
Davis King committed
68
        // the value 2 since var was incremented by subtask().
69
70
71
72
        int result = var;
        dlog << LINFO << "var = " << result;

        // Wait for all the tasks we have started to finish.  Note that
73
74
75
76
        // 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.
Davis King's avatar
Davis King committed
77
78
        tp.wait_for_all_tasks();

79
        dlog << LINFO << "mytask end" ;
Davis King's avatar
Davis King committed
80
81
    }

82
    void subtask(int& a)
Davis King's avatar
Davis King committed
83
84
    {
        dlib::sleep(200);
85
86
        a = a + 1;
        dlog << LINFO << "subtask end ";
Davis King's avatar
Davis King committed
87
88
    }

89
    void subtask2()
Davis King's avatar
Davis King committed
90
    {
91
92
        dlib::sleep(300);
        dlog << LINFO << "subtask2 end ";
Davis King's avatar
Davis King committed
93
94
95
96
97
98
    }

};

// ----------------------------------------------------------------------------------------

99
int main() try
Davis King's avatar
Davis King committed
100
101
102
103
104
105
106
{
    // tell the logger to print out everything
    dlog.set_level(LALL);


    dlog << LINFO << "schedule a few tasks";

107
108
109
110
111
112
    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);
Davis King's avatar
Davis King committed
113

114
115
116
117
118
119
    // 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.
Davis King's avatar
Davis King committed
120

121
122


123
124
125
126
127
128
129
130
    // 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
131
132


133
134
135
136
137
138
    // 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);
139
    for (unsigned long i = 0; i < vect.size(); ++i)
140
141
        vect[i] = dlib::async(tp, [i]() { return i*i; });
    // Print the results
142
    for (unsigned long i = 0; i < vect.size(); ++i)
143
144
        dlog << LINFO << "vect["<<i<<"]: " << vect[i].get();

Davis King's avatar
Davis King committed
145

146
147
148
149
150
151
152
    // 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();
Davis King's avatar
Davis King committed
153
154


155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
    /* 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;
}
Davis King's avatar
Davis King committed
182
183