1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2013-2020 Red Hat, Inc.
5 //
6 // Author: Dodji Seketeli
7
8 /// @file
9 ///
10 /// This file implements the worker threads (or thread pool) design
11 /// pattern. It aims at performing a set of tasks in parallel, using
12 /// the multi-threading capabilities of the underlying processor(s).
13
14 #include <assert.h>
15 #include <unistd.h>
16 #include <pthread.h>
17 #include <queue>
18 #include <vector>
19 #include <iostream>
20
21 #include "abg-fwd.h"
22 #include "abg-internal.h"
23 // <headers defining libabigail's API go under here>
24 ABG_BEGIN_EXPORT_DECLARATIONS
25
26 #include "abg-workers.h"
27
28 ABG_END_EXPORT_DECLARATIONS
29 // </headers defining libabigail's API>
30
31 namespace abigail
32 {
33
34 namespace workers
35 {
36
37 /// @defgroup thread_pool Worker Threads
38 /// @{
39 ///
40 /// \brief Libabigail's implementation of Thread Pools.
41 ///
42 /// The main interface of this pattern is a @ref queue of @ref tasks
43 /// to be performed. Associated to that queue are a set of worker
44 /// threads (these are native posix threads) that sits there, idle,
45 /// until at least one @ref task is added to the queue.
46 ///
47 /// When a @ref task is added to the @ref queue, one thread is woken
48 /// up, picks the @ref task, removes it from the @ref queue, and
49 /// executes the instructions it carries. We say the worker thread
50 /// performs the @ref task.
51 ///
52 /// When the worker thread is done performing the @ref task, the
53 /// performed @ref task is added to another queue, named as the "done
54 /// queue". Then the thread looks at the @ref queue of tasks to be
55 /// performed again, and if there is at least one task in that queue,
56 /// the same process as above is done. Otherwise, the thread blocks,
57 /// waiting for a new task to be added to the queue.
58 ///
59 /// By default, the number of worker threads is equal to the number of
60 /// execution threads advertised by the underlying processor.
61 ///
62 /// Note that the user of the queue can either wait for all the tasks
63 /// to be performed by the pool of threads,and them stop them, get the
64 /// vector of done tasks and proceed to whatever computation she may
65 /// need next.
66 ///
67 /// Or she can choose to be asynchronously notified whenever a task is
68 /// performed and added to the "done queue".
69 ///
70 ///@}
71
72 /// @return The number of hardware threads of executions advertised by
73 /// the underlying processor.
74 size_t
get_number_of_threads()75 get_number_of_threads()
76 {return sysconf(_SC_NPROCESSORS_ONLN);}
77
78 /// The abstraction of a worker thread.
79 ///
80 /// This is an implementation detail of the @ref queue public
81 /// interface type of this worker thread design pattern.
82 struct worker
83 {
84 pthread_t tid;
85
workerabigail::workers::worker86 worker()
87 : tid()
88 {}
89
90 static queue::priv*
91 wait_to_execute_a_task(queue::priv*);
92 }; // end struct worker
93
94 // </worker declarations>
95
96 // <queue stuff>
97
98 /// The private data structure of the task queue.
99 struct queue::priv
100 {
101 // A boolean to say if the user wants to shutdown the worker
102 // threads. guarded by tasks_todo_mutex.
103 // TODO: once we have std::atomic<bool>, use it and reconsider the
104 // synchronization around its reads and writes
105 bool bring_workers_down;
106 // The number of worker threads.
107 size_t num_workers;
108 // A mutex that protects the todo tasks queue from being accessed in
109 // read/write by two threads at the same time.
110 pthread_mutex_t tasks_todo_mutex;
111 // The queue condition variable. This condition is used to make the
112 // worker threads sleep until a new task is added to the queue of
113 // todo tasks. Whenever a new task is added to that queue, a signal
114 // is sent to all a thread sleeping on this condition variable.
115 pthread_cond_t tasks_todo_cond;
116 // A mutex that protects the done tasks queue from being accessed in
117 // read/write by two threads at the same time.
118 pthread_mutex_t tasks_done_mutex;
119 // A condition to be signalled whenever there is a task done. That is being
120 // used to wait for tasks completed when bringing the workers down.
121 pthread_cond_t tasks_done_cond;
122 // The todo task queue itself.
123 std::queue<task_sptr> tasks_todo;
124 // The done task queue itself.
125 std::vector<task_sptr> tasks_done;
126 // This functor is invoked to notify the user of this queue that a
127 // task has been completed and has been added to the done tasks
128 // vector. We call it a notifier. This notifier is the default
129 // notifier of the work queue; the one that is used when the user
130 // has specified no notifier. It basically does nothing.
131 static task_done_notify default_notify;
132 // This is a reference to the the notifier that is actually used in
133 // the queue. It's either the one specified by the user or the
134 // default one.
135 task_done_notify& notify;
136 // A vector of the worker threads.
137 std::vector<worker> workers;
138
139 /// A constructor of @ref queue::priv.
140 ///
141 /// @param nb_workers the number of worker threads to have in the
142 /// thread pool.
143 ///
144 /// @param task_done_notify a functor object that is invoked by the
145 /// worker thread which has performed the task, right after it's
146 /// added that task to the vector of the done tasks.
privabigail::workers::queue::priv147 priv(size_t nb_workers = get_number_of_threads(),
148 task_done_notify& n = default_notify)
149 : bring_workers_down(),
150 num_workers(nb_workers),
151 tasks_todo_mutex(),
152 tasks_todo_cond(),
153 tasks_done_mutex(),
154 tasks_done_cond(),
155 notify(n)
156 {create_workers();}
157
158 /// Create the worker threads pool and have all threads sit idle,
159 /// waiting for a task to be added to the todo queue.
160 void
create_workersabigail::workers::queue::priv161 create_workers()
162 {
163 for (unsigned i = 0; i < num_workers; ++i)
164 {
165 worker w;
166 ABG_ASSERT(pthread_create(&w.tid,
167 /*attr=*/0,
168 (void*(*)(void*))&worker::wait_to_execute_a_task,
169 this) == 0);
170 workers.push_back(w);
171 }
172 }
173
174 /// Submit a task to the queue of tasks to be performed.
175 ///
176 /// This wakes up one thread from the pool which immediatly starts
177 /// performing the task. When it's done with the task, it goes back
178 /// to be suspended, waiting for a new task to be scheduled.
179 ///
180 /// @param t the task to schedule. Note that a nil task won't be
181 /// scheduled. If the queue is empty, the task @p t won't be
182 /// scheduled either.
183 ///
184 /// @return true iff the task @p t was successfully scheduled.
185 bool
schedule_taskabigail::workers::queue::priv186 schedule_task(const task_sptr& t)
187 {
188 if (workers.empty() || !t)
189 return false;
190
191 pthread_mutex_lock(&tasks_todo_mutex);
192 tasks_todo.push(t);
193 pthread_mutex_unlock(&tasks_todo_mutex);
194 pthread_cond_signal(&tasks_todo_cond);
195 return true;
196 }
197
198 /// Submit a vector of task to the queue of tasks to be performed.
199 ///
200 /// This wakes up threads of the pool which immediatly start
201 /// performing the tasks. When they are done with the task, they go
202 /// back to be suspended, waiting for new tasks to be scheduled.
203 ///
204 /// @param tasks the tasks to schedule.
205 bool
schedule_tasksabigail::workers::queue::priv206 schedule_tasks(const tasks_type& tasks)
207 {
208 bool is_ok= true;
209 for (tasks_type::const_iterator t = tasks.begin(); t != tasks.end(); ++t)
210 is_ok &= schedule_task(*t);
211 return is_ok;
212 }
213
214 /// Signal all the threads (of the pool) which are suspended and
215 /// waiting to perform a task, so that they wake up and end up their
216 /// execution. If there is no task to perform, they just end their
217 /// execution. If there are tasks to perform, they finish them and
218 /// then end their execution.
219 ///
220 /// This function then joins all the tasks of the pool, waiting for
221 /// them to finish, and then it returns. In other words, this
222 /// function suspends the thread of the caller, waiting for the
223 /// worker threads to finish their tasks, and end their execution.
224 ///
225 /// If the user code wants to work with the thread pool again,
226 /// she'll need to create them again, using the member function
227 /// create_workers().
228 void
do_bring_workers_downabigail::workers::queue::priv229 do_bring_workers_down()
230 {
231 if (workers.empty())
232 return;
233
234 // Wait for the todo list to be empty to make sure all tasks got picked up
235 pthread_mutex_lock(&tasks_todo_mutex);
236 while (!tasks_todo.empty())
237 pthread_cond_wait(&tasks_done_cond, &tasks_todo_mutex);
238
239 bring_workers_down = true;
240 pthread_mutex_unlock(&tasks_todo_mutex);
241
242 // Now that the task queue is empty, drain the workers by waking them up,
243 // letting them finish their final task before termination.
244 ABG_ASSERT(pthread_cond_broadcast(&tasks_todo_cond) == 0);
245
246 for (std::vector<worker>::const_iterator i = workers.begin();
247 i != workers.end();
248 ++i)
249 ABG_ASSERT(pthread_join(i->tid, /*thread_return=*/0) == 0);
250 workers.clear();
251 }
252
253 /// Destructors of @ref queue::priv type.
~privabigail::workers::queue::priv254 ~priv()
255 {do_bring_workers_down();}
256
257 }; //end struct queue::priv
258
259 // default initialize the default notifier.
260 queue::task_done_notify queue::priv::default_notify;
261
262 /// Default constructor of the @ref queue type.
263 ///
264 /// By default the queue is created with a number of worker threaders
265 /// which is equals to the number of simultaneous execution threads
266 /// supported by the underlying processor.
queue()267 queue::queue()
268 : p_(new priv())
269 {}
270
271 /// Constructor of the @ref queue type.
272 ///
273 /// @param number_of_workers the number of worker threads to have in
274 /// the pool.
queue(unsigned number_of_workers)275 queue::queue(unsigned number_of_workers)
276 : p_(new priv(number_of_workers))
277 {}
278
279 /// Constructor of the @ref queue type.
280 ///
281 /// @param number_of_workers the number of worker threads to have in
282 /// the pool.
283 ///
284 /// @param the notifier to invoke when a task is done doing its job.
285 /// Users should create a type that inherit this @ref task_done_notify
286 /// class and overload its virtual task_done_notify::operator()
287 /// operator function. Note that the code of that
288 /// task_done_notify::operator() is assured to run in *sequence*, with
289 /// respect to the code of other task_done_notify::operator() from
290 /// other tasks.
queue(unsigned number_of_workers,task_done_notify & notifier)291 queue::queue(unsigned number_of_workers,
292 task_done_notify& notifier)
293 : p_(new priv(number_of_workers, notifier))
294 {}
295
296 /// Getter of the size of the queue. This gives the number of task
297 /// still present in the queue.
298 ///
299 /// @return the number of task still present in the queue.
300 size_t
get_size() const301 queue::get_size() const
302 {return p_->tasks_todo.size();}
303
304 /// Submit a task to the queue of tasks to be performed.
305 ///
306 /// This wakes up one thread from the pool which immediatly starts
307 /// performing the task. When it's done with the task, it goes back
308 /// to be suspended, waiting for a new task to be scheduled.
309 ///
310 /// @param t the task to schedule. Note that if the queue is empty or
311 /// if the task is nil, the task is not scheduled.
312 ///
313 /// @return true iff the task was successfully scheduled.
314 bool
schedule_task(const task_sptr & t)315 queue::schedule_task(const task_sptr& t)
316 {return p_->schedule_task(t);}
317
318 /// Submit a vector of tasks to the queue of tasks to be performed.
319 ///
320 /// This wakes up one or more threads from the pool which immediatly
321 /// start performing the tasks. When the threads are done with the
322 /// tasks, they goes back to be suspended, waiting for a new task to
323 /// be scheduled.
324 ///
325 /// @param tasks the tasks to schedule.
326 bool
schedule_tasks(const tasks_type & tasks)327 queue::schedule_tasks(const tasks_type& tasks)
328 {return p_->schedule_tasks(tasks);}
329
330 /// Suspends the current thread until all worker threads finish
331 /// performing the tasks they are executing.
332 ///
333 /// If the worker threads were suspended waiting for a new task to
334 /// perform, they are woken up and their execution ends.
335 ///
336 /// The execution of the current thread is resumed when all the
337 /// threads of the pool have finished their execution and are
338 /// terminated.
339 void
wait_for_workers_to_complete()340 queue::wait_for_workers_to_complete()
341 {p_->do_bring_workers_down();}
342
343 /// Getter of the vector of tasks that got performed.
344 ///
345 /// @return the vector of tasks that got performed.
346 std::vector<task_sptr>&
get_completed_tasks() const347 queue::get_completed_tasks() const
348 {return p_->tasks_done;}
349
350 /// Destructor for the @ref queue type.
~queue()351 queue::~queue()
352 {}
353
354 /// The default function invocation operator of the @ref queue type.
355 ///
356 /// This does nothing.
357 void
operator ()(const task_sptr &)358 queue::task_done_notify::operator()(const task_sptr&/*task_done*/)
359 {
360 }
361
362 // </queue stuff>
363
364 // <worker definitions>
365
366 /// Wait to be woken up by a thread condition signal, then look if
367 /// there is a task to be executed. If there is, then pick one (in a
368 /// FIFO manner), execute it, and put the executed task into the set
369 /// of done tasks.
370 ///
371 /// @param t the private data of the "task queue" type to consider.
372 ///
373 /// @param return the same private data of the task queue type we got
374 /// in argument.
375 queue::priv*
wait_to_execute_a_task(queue::priv * p)376 worker::wait_to_execute_a_task(queue::priv* p)
377 {
378 while (true)
379 {
380 pthread_mutex_lock(&p->tasks_todo_mutex);
381 // If there is no more tasks to perform and the queue is not to
382 // be brought down then wait (sleep) for new tasks to come up.
383 while (p->tasks_todo.empty() && !p->bring_workers_down)
384 pthread_cond_wait(&p->tasks_todo_cond, &p->tasks_todo_mutex);
385
386 // We were woken up. So maybe there are tasks to perform? If
387 // so, get a task from the queue ...
388 task_sptr t;
389 if (!p->tasks_todo.empty())
390 {
391 t = p->tasks_todo.front();
392 p->tasks_todo.pop();
393 }
394 pthread_mutex_unlock(&p->tasks_todo_mutex);
395
396 // If we've got a task to perform then perform it and when it's
397 // done then add to the set of tasks that are done.
398 if (t)
399 {
400 t->perform();
401
402 // Add the task to the vector of tasks that are done and
403 // notify listeners about the fact that the task is done.
404 //
405 // Note that this (including the notification) is not
406 // happening in parallel. So the code performed by the
407 // notifier during the notification is running sequentially,
408 // not in parallel with any other task that was just done
409 // and that is notifying its listeners.
410 pthread_mutex_lock(&p->tasks_done_mutex);
411 p->tasks_done.push_back(t);
412 p->notify(t);
413 pthread_mutex_unlock(&p->tasks_done_mutex);
414 pthread_cond_signal(&p->tasks_done_cond);
415 }
416
417 // ensure we access bring_workers_down always guarded
418 bool drop_out = false;
419 pthread_mutex_lock(&p->tasks_todo_mutex);
420 drop_out = p->bring_workers_down;
421 pthread_mutex_unlock(&p->tasks_todo_mutex);
422 if (drop_out)
423 break;
424 }
425
426 return p;
427 }
428 // </worker definitions>
429 } //end namespace workers
430 } //end namespace abigail
431