@@ -32,11 +32,8 @@ namespace llvm {
32
32
33
33
class ThreadPoolTaskGroup ;
34
34
35
- // / A ThreadPool for asynchronous parallel execution on a defined number of
36
- // / threads.
37
- // /
38
- // / The pool keeps a vector of threads alive, waiting on a condition variable
39
- // / for some work to become available.
35
+ // / This defines the abstract base interface for a ThreadPool allowing
36
+ // / asynchronous parallel execution on a defined number of threads.
40
37
// /
41
38
// / It is possible to reuse one thread pool for different groups of tasks
42
39
// / by grouping tasks using ThreadPoolTaskGroup. All tasks are processed using
@@ -49,16 +46,31 @@ class ThreadPoolTaskGroup;
49
46
// / available threads are used up by tasks waiting for a task that has no thread
50
47
// / left to run on (this includes waiting on the returned future). It should be
51
48
// / generally safe to wait() for a group as long as groups do not form a cycle.
52
- class ThreadPool {
49
+ class ThreadPoolInterface {
50
+ // / The actual method to enqueue a task to be defined by the concrete
51
+ // / implementation.
52
+ virtual void asyncEnqueue (std::function<void ()> Task,
53
+ ThreadPoolTaskGroup *Group) = 0;
54
+
53
55
public:
54
- // / Construct a pool using the hardware strategy \p S for mapping hardware
55
- // / execution resources (threads, cores, CPUs)
56
- // / Defaults to using the maximum execution resources in the system, but
57
- // / accounting for the affinity mask.
58
- ThreadPool (ThreadPoolStrategy S = hardware_concurrency());
56
+ // / Destroying the pool will drain the pending tasks and wait. The current
57
+ // / thread may participate in the execution of the pending tasks.
58
+ virtual ~ThreadPoolInterface ();
59
59
60
- // / Blocking destructor: the pool will wait for all the threads to complete.
61
- ~ThreadPool ();
60
+ // / Blocking wait for all the threads to complete and the queue to be empty.
61
+ // / It is an error to try to add new tasks while blocking on this call.
62
+ // / Calling wait() from a task would deadlock waiting for itself.
63
+ virtual void wait () = 0;
64
+
65
+ // / Blocking wait for only all the threads in the given group to complete.
66
+ // / It is possible to wait even inside a task, but waiting (directly or
67
+ // / indirectly) on itself will deadlock. If called from a task running on a
68
+ // / worker thread, the call may process pending tasks while waiting in order
69
+ // / not to waste the thread.
70
+ virtual void wait (ThreadPoolTaskGroup &Group) = 0;
71
+
72
+ // / Returns the maximum number of worker this pool can eventually grow to.
73
+ virtual unsigned getMaxConcurrency () const = 0;
62
74
63
75
// / Asynchronous submission of a task to the pool. The returned future can be
64
76
// / used to wait for the task to finish and is *non-blocking* on destruction.
@@ -92,102 +104,84 @@ class ThreadPool {
92
104
&Group);
93
105
}
94
106
107
+ private:
108
+ // / Asynchronous submission of a task to the pool. The returned future can be
109
+ // / used to wait for the task to finish and is *non-blocking* on destruction.
110
+ template <typename ResTy>
111
+ std::shared_future<ResTy> asyncImpl (std::function<ResTy()> Task,
112
+ ThreadPoolTaskGroup *Group) {
113
+ auto Future = std::async (std::launch::deferred, std::move (Task)).share ();
114
+ asyncEnqueue ([Future]() { Future.wait (); }, Group);
115
+ return Future;
116
+ }
117
+ };
118
+
119
+ // / A ThreadPool implementation using std::threads.
120
+ // /
121
+ // / The pool keeps a vector of threads alive, waiting on a condition variable
122
+ // / for some work to become available.
123
+ class StdThreadPool : public ThreadPoolInterface {
124
+ public:
125
+ // / Construct a pool using the hardware strategy \p S for mapping hardware
126
+ // / execution resources (threads, cores, CPUs)
127
+ // / Defaults to using the maximum execution resources in the system, but
128
+ // / accounting for the affinity mask.
129
+ StdThreadPool (ThreadPoolStrategy S = hardware_concurrency());
130
+
131
+ // / Blocking destructor: the pool will wait for all the threads to complete.
132
+ ~StdThreadPool () override ;
133
+
95
134
// / Blocking wait for all the threads to complete and the queue to be empty.
96
135
// / It is an error to try to add new tasks while blocking on this call.
97
136
// / Calling wait() from a task would deadlock waiting for itself.
98
- void wait ();
137
+ void wait () override ;
99
138
100
139
// / Blocking wait for only all the threads in the given group to complete.
101
140
// / It is possible to wait even inside a task, but waiting (directly or
102
141
// / indirectly) on itself will deadlock. If called from a task running on a
103
142
// / worker thread, the call may process pending tasks while waiting in order
104
143
// / not to waste the thread.
105
- void wait (ThreadPoolTaskGroup &Group);
144
+ void wait (ThreadPoolTaskGroup &Group) override ;
106
145
107
- // Returns the maximum number of worker threads in the pool, not the current
108
- // number of threads!
109
- unsigned getMaxConcurrency () const { return MaxThreadCount; }
146
+ // / Returns the maximum number of worker threads in the pool, not the current
147
+ // / number of threads!
148
+ unsigned getMaxConcurrency () const override { return MaxThreadCount; }
110
149
111
- // TODO: misleading legacy name warning!
150
+ // TODO: Remove, misleading legacy name warning!
112
151
LLVM_DEPRECATED (" Use getMaxConcurrency instead" , " getMaxConcurrency" )
113
152
unsigned getThreadCount () const { return MaxThreadCount; }
114
153
115
154
// / Returns true if the current thread is a worker thread of this thread pool.
116
155
bool isWorkerThread () const ;
117
156
118
157
private:
119
- // / Helpers to create a promise and a callable wrapper of \p Task that sets
120
- // / the result of the promise. Returns the callable and a future to access the
121
- // / result.
122
- template <typename ResTy>
123
- static std::pair<std::function<void ()>, std::future<ResTy>>
124
- createTaskAndFuture (std::function<ResTy()> Task) {
125
- std::shared_ptr<std::promise<ResTy>> Promise =
126
- std::make_shared<std::promise<ResTy>>();
127
- auto F = Promise->get_future ();
128
- return {
129
- [Promise = std::move (Promise), Task]() { Promise->set_value (Task ()); },
130
- std::move (F)};
131
- }
132
- static std::pair<std::function<void ()>, std::future<void >>
133
- createTaskAndFuture (std::function<void ()> Task) {
134
- std::shared_ptr<std::promise<void >> Promise =
135
- std::make_shared<std::promise<void >>();
136
- auto F = Promise->get_future ();
137
- return {[Promise = std::move (Promise), Task]() {
138
- Task ();
139
- Promise->set_value ();
140
- },
141
- std::move (F)};
142
- }
143
-
144
158
// / Returns true if all tasks in the given group have finished (nullptr means
145
159
// / all tasks regardless of their group). QueueLock must be locked.
146
160
bool workCompletedUnlocked (ThreadPoolTaskGroup *Group) const ;
147
161
148
162
// / Asynchronous submission of a task to the pool. The returned future can be
149
163
// / used to wait for the task to finish and is *non-blocking* on destruction.
150
- template <typename ResTy>
151
- std::shared_future<ResTy> asyncImpl (std::function<ResTy()> Task,
152
- ThreadPoolTaskGroup *Group) {
153
-
154
- #if LLVM_ENABLE_THREADS
155
- // / Wrap the Task in a std::function<void()> that sets the result of the
156
- // / corresponding future.
157
- auto R = createTaskAndFuture (Task);
158
-
164
+ void asyncEnqueue (std::function<void ()> Task,
165
+ ThreadPoolTaskGroup *Group) override {
159
166
int requestedThreads;
160
167
{
161
168
// Lock the queue and push the new task
162
169
std::unique_lock<std::mutex> LockGuard (QueueLock);
163
170
164
171
// Don't allow enqueueing after disabling the pool
165
172
assert (EnableFlag && " Queuing a thread during ThreadPool destruction" );
166
- Tasks.emplace_back (std::make_pair (std::move (R. first ), Group));
173
+ Tasks.emplace_back (std::make_pair (std::move (Task ), Group));
167
174
requestedThreads = ActiveThreads + Tasks.size ();
168
175
}
169
176
QueueCondition.notify_one ();
170
177
grow (requestedThreads);
171
- return R.second .share ();
172
-
173
- #else // LLVM_ENABLE_THREADS Disabled
174
-
175
- // Get a Future with launch::deferred execution using std::async
176
- auto Future = std::async (std::launch::deferred, std::move (Task)).share ();
177
- // Wrap the future so that both ThreadPool::wait() can operate and the
178
- // returned future can be sync'ed on.
179
- Tasks.emplace_back (std::make_pair ([Future]() { Future.get (); }, Group));
180
- return Future;
181
- #endif
182
178
}
183
179
184
- #if LLVM_ENABLE_THREADS
185
- // Grow to ensure that we have at least `requested` Threads, but do not go
186
- // over MaxThreadCount.
180
+ // / Grow to ensure that we have at least `requested` Threads, but do not go
181
+ // / over MaxThreadCount.
187
182
void grow (int requested);
188
183
189
184
void processTasks (ThreadPoolTaskGroup *WaitingForGroup);
190
- #endif
191
185
192
186
// / Threads in flight
193
187
std::vector<llvm::thread> Threads;
@@ -209,25 +203,66 @@ class ThreadPool {
209
203
// / Number of threads active for tasks in the given group (only non-zero).
210
204
DenseMap<ThreadPoolTaskGroup *, unsigned > ActiveGroups;
211
205
212
- #if LLVM_ENABLE_THREADS // avoids warning for unused variable
213
206
// / Signal for the destruction of the pool, asking thread to exit.
214
207
bool EnableFlag = true ;
215
- #endif
216
208
217
209
const ThreadPoolStrategy Strategy;
218
210
219
211
// / Maximum number of threads to potentially grow this pool to.
220
212
const unsigned MaxThreadCount;
221
213
};
222
214
215
+ // / A non-threaded implementation.
216
+ class SingleThreadExecutor : public ThreadPoolInterface {
217
+ public:
218
+ // / Construct a non-threaded pool, ignoring using the hardware strategy.
219
+ SingleThreadExecutor (ThreadPoolStrategy ignored = {});
220
+
221
+ // / Blocking destructor: the pool will first execute the pending tasks.
222
+ ~SingleThreadExecutor () override ;
223
+
224
+ // / Blocking wait for all the tasks to execute first
225
+ void wait () override ;
226
+
227
+ // / Blocking wait for only all the tasks in the given group to complete.
228
+ void wait (ThreadPoolTaskGroup &Group) override ;
229
+
230
+ // / Returns always 1: there is no concurrency.
231
+ unsigned getMaxConcurrency () const override { return 1 ; }
232
+
233
+ // TODO: Remove, misleading legacy name warning!
234
+ LLVM_DEPRECATED (" Use getMaxConcurrency instead" , " getMaxConcurrency" )
235
+ unsigned getThreadCount () const { return 1 ; }
236
+
237
+ // / Returns true if the current thread is a worker thread of this thread pool.
238
+ bool isWorkerThread () const ;
239
+
240
+ private:
241
+ // / Asynchronous submission of a task to the pool. The returned future can be
242
+ // / used to wait for the task to finish and is *non-blocking* on destruction.
243
+ void asyncEnqueue (std::function<void ()> Task,
244
+ ThreadPoolTaskGroup *Group) override {
245
+ Tasks.emplace_back (std::make_pair (std::move (Task), Group));
246
+ }
247
+
248
+ // / Tasks waiting for execution in the pool.
249
+ std::deque<std::pair<std::function<void ()>, ThreadPoolTaskGroup *>> Tasks;
250
+ };
251
+
252
+ #if LLVM_ENABLE_THREADS
253
+ using ThreadPool = StdThreadPool;
254
+ #else
255
+ using ThreadPool = SingleThreadExecutor;
256
+ #endif
257
+
223
258
// / A group of tasks to be run on a thread pool. Thread pool tasks in different
224
259
// / groups can run on the same threadpool but can be waited for separately.
225
260
// / It is even possible for tasks of one group to submit and wait for tasks
226
261
// / of another group, as long as this does not form a loop.
227
262
class ThreadPoolTaskGroup {
228
263
public:
229
264
// / The ThreadPool argument is the thread pool to forward calls to.
230
- ThreadPoolTaskGroup (ThreadPool &Pool) : Pool(Pool) {}
265
+ ThreadPoolTaskGroup (ThreadPoolInterface &Pool) : Pool(Pool) {}
231
266
232
267
// / Blocking destructor: will wait for all the tasks in the group to complete
233
268
// / by calling ThreadPool::wait().
@@ -244,7 +279,7 @@ class ThreadPoolTaskGroup {
244
279
void wait () { Pool.wait (*this ); }
245
280
246
281
private:
247
- ThreadPool &Pool;
282
+ ThreadPoolInterface &Pool;
248
283
};
249
284
250
285
} // namespace llvm
0 commit comments