Maestro 0.3.11
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
ThreadsPool.h
Go to the documentation of this file.
11
12#pragma once
13
14#ifndef __THREADS_POOL_H_
15#define __THREADS_POOL_H_
16
17#include <algorithm>
18#include <condition_variable>
19#include <limits>
20#include <mutex>
21#include <new>
22#include <queue>
23#include <vector>
24
25#include <fstream>
26
27#if defined(__unix__) || defined(__APPLE__)
28#include <pthread.h>
29#define MAESTRO_POOL_FORK_HANDLERS 1
30#endif
31
32#include "WorkerThread.h"
33
34namespace Utils
35{
36
37#ifdef MAESTRO_POOL_FORK_HANDLERS
38namespace Private
39{
40// Pools keep their threads between executions, but threads do not survive
41// fork(): a child would queue jobs for workers that do not exist. Each pool
42// holds its locks across fork(); in the child an idle pool drops its stale
43// workers and creates new ones on its next Resize(). Threads are not stopped
44// before fork(): their exit would wait on OpenMP's own fork locks.
45class ForkAwarePool
46{
47 public:
48 virtual ~ForkAwarePool() = default;
49 virtual void PrepareFork() = 0;
50 virtual void AfterForkParent() = 0;
51 virtual void AfterForkChild() = 0;
52};
53
54class PoolForkRegistry
55{
56 public:
57 static PoolForkRegistry &Instance()
58 {
59 // Never destroyed: pools owned by static objects unregister at exit.
60 static auto *registry = new PoolForkRegistry;
61 return *registry;
62 }
63
64 void Add(ForkAwarePool *pool)
65 {
66 std::call_once(installed, [] { pthread_atfork(&Prepare, &Parent, &Child); });
67 const std::lock_guard lock(mutex);
68 pools.push_back(pool);
69 }
70
71 void Remove(ForkAwarePool *pool)
72 {
73 const std::lock_guard lock(mutex);
74 pools.erase(std::remove(pools.begin(), pools.end(), pool), pools.end());
75 }
76
77 private:
78 // The registry lock is held from Prepare() until Parent()/Child().
79 static void Prepare()
80 {
81 auto &registry = Instance();
82 registry.mutex.lock();
83 for (auto *pool : registry.pools)
84 pool->PrepareFork();
85 }
86
87 static void Parent()
88 {
89 auto &registry = Instance();
90 for (auto *pool : registry.pools)
91 pool->AfterForkParent();
92 registry.mutex.unlock();
93 }
94
95 static void Child()
96 {
97 auto &registry = Instance();
98 for (auto *pool : registry.pools)
99 pool->AfterForkChild();
100 registry.mutex.unlock();
101 }
102
103 std::mutex mutex;
104 std::vector<ForkAwarePool *> pools;
105 std::once_flag installed;
106};
107} // namespace Private
108#endif
109
121template <class Job>
123#ifdef MAESTRO_POOL_FORK_HANDLERS
124 : private Private::ForkAwarePool
125#endif
126{
127 using JobWorkerThread = WorkerThread<ThreadsPool<Job>, Job>;
128 friend class WorkerThread<ThreadsPool<Job>, Job>;
129
130 public:
138 explicit ThreadsPool(int nrThreads = 0)
139 {
140 if (nrThreads <= 0)
141 nrThreads = 1;
142
143 for (int i = 0; i < nrThreads; ++i)
144 Threads.emplace_back(std::make_unique<JobWorkerThread>(this));
145#ifdef MAESTRO_POOL_FORK_HANDLERS
146 Private::PoolForkRegistry::Instance().Add(this);
147#endif
148 }
149
150 ThreadsPool(const ThreadsPool &) = delete;
152
160 {
161#ifdef MAESTRO_POOL_FORK_HANDLERS
162 Private::PoolForkRegistry::Instance().Remove(this);
163#endif
164 Stop();
165 }
166
173 void Stop()
174 {
175 const std::lock_guard lifecycle(LifecycleMutex);
176 {
177 std::lock_guard lock(Mutex);
178
179 for (auto &worker : Threads)
180 worker->SetStopUnlocked();
181 }
182
183 NotifyAll();
184
185 for (auto &worker : Threads)
186 worker->Join();
187
188 Threads.clear();
189 }
190
196 void Start()
197 {
198 for (auto &worker : Threads)
199 worker->Start();
200 }
201
209 void AddRunJob(const std::shared_ptr<Job> &job)
210 {
211 {
212 std::lock_guard lock(Mutex);
213 JobsQueue.push(job);
214 }
215
216 NotifyOne();
217 }
218
226 void AddRunJob(std::shared_ptr<Job> &&job)
227 {
228 {
229 std::lock_guard lock(Mutex);
230 JobsQueue.push(std::move(job));
231 }
232
233 NotifyOne();
234 }
235
245 {
246 do
247 {
248 std::unique_lock lock(FinishMutex);
249
250 if (FinishCount >= FinishLimit)
251 return;
252
253 ConditionFinish.wait(lock, [this] { return FinishCount >= FinishLimit; });
254
255 if (FinishCount >= FinishLimit)
256 return;
257 } while (true);
258 }
259
270 void Resize(size_t nrThreads)
271 {
272 if (nrThreads <= 0)
273 nrThreads = 1;
274
275 const std::lock_guard lifecycle(LifecycleMutex);
276 size_t oldSize = Threads.size();
277
278 if (oldSize == nrThreads)
279 return;
280
281 if (oldSize < nrThreads)
282 {
283 for (size_t i = oldSize; i < nrThreads; ++i)
284 {
285 Threads.emplace_back(std::make_unique<JobWorkerThread>(this));
286 Threads.back()->Start();
287 }
288 }
289 else
290 {
291 {
292 std::lock_guard lock(Mutex);
293 for (size_t i = nrThreads; i < oldSize; ++i)
294 Threads[i]->SetStopUnlocked();
295 }
296
297 NotifyAll();
298
299 for (size_t i = nrThreads; i < oldSize; ++i)
300 Threads[i]->Join();
301 Threads.resize(nrThreads);
302 }
303 }
304
315 void SetFinishLimit(size_t limit)
316 {
317 std::unique_lock lock(FinishMutex);
318 FinishLimit = limit;
319 FinishCount = 0;
320 }
321
322 private:
323#ifdef MAESTRO_POOL_FORK_HANDLERS
324 void PrepareFork() override
325 {
326 // The locks are released by AfterForkParent() or AfterForkChild().
327 LifecycleMutex.lock();
328 Mutex.lock();
329 FinishMutex.lock();
330 idleAtFork = JobsQueue.empty() && Running == 0;
331 }
332
333 void AfterForkParent() override
334 {
335 FinishMutex.unlock();
336 Mutex.unlock();
337 LifecycleMutex.unlock();
338 }
339
340 void AfterForkChild() override
341 {
342 // Only the forking thread exists here. A pool in the middle of an
343 // execution is left as it is; an idle one forgets its workers, whose
344 // waits also left the copied condition variable unusable. Threads
345 // that were waiting for jobs to finish are gone too.
346 if (idleAtFork)
347 {
348 for (auto &worker : Threads)
349 worker->Abandon();
350 Threads.clear();
351 new (&Condition) std::condition_variable;
352 }
353 new (&ConditionFinish) std::condition_variable;
354 new (&FinishMutex) std::mutex;
355 new (&Mutex) std::mutex;
356 new (&LifecycleMutex) std::mutex;
357 }
358#endif
359
365 inline void NotifyOne()
366 {
367 Condition.notify_one();
368 }
369
376 inline void NotifyAll()
377 {
378 Condition.notify_all();
379 }
380
387 inline bool HasWork() const
388 {
389 return !JobsQueue.empty();
390 }
391
399 void NotifyFinish()
400 {
401 ConditionFinish.notify_all();
402 }
403
404 std::mutex LifecycleMutex;
405 std::mutex Mutex;
406 std::condition_variable Condition;
407
408 std::mutex FinishMutex;
409 std::condition_variable ConditionFinish;
411
412 size_t FinishCount = 0;
413 size_t FinishLimit = std::numeric_limits<size_t>::max();
415
416 std::queue<std::shared_ptr<Job>> JobsQueue;
417 size_t Running = 0;
418 bool idleAtFork = false;
419
420 std::vector<std::unique_ptr<JobWorkerThread>> Threads;
421};
422
423} // namespace Utils
424
425#endif // __THREADS_POOL_H_
void Start()
Start all the threads in the threads pool.
void AddRunJob(const std::shared_ptr< Job > &job)
Add a job to be executed by the threads pool.
ThreadsPool(const ThreadsPool &)=delete
void WaitForFinish()
Wait for all jobs to finish.
void SetFinishLimit(size_t limit)
Set the finish limit.
void AddRunJob(std::shared_ptr< Job > &&job)
Add a job to be executed by the threads pool.
void Resize(size_t nrThreads)
Resize the threads pool.
ThreadsPool & operator=(const ThreadsPool &)=delete
void Stop()
Stop all the threads in the threads pool.
~ThreadsPool()
Destructor.
ThreadsPool(int nrThreads=0)
Construct a new Thread pool object.
WorkerThread class for a thread in a threads pool.
Portable dense contraction kernels.