@@ -32,11 +32,9 @@ 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
+
36
+ // / This defines the abstract base interface for a ThreadPool allowing
37
+ // / asynchronous parallel execution on a defined number of threads.
40
38
// /
41
39
// / It is possible to reuse one thread pool for different groups of tasks
42
40
// / by grouping tasks using ThreadPoolTaskGroup. All tasks are processed using
@@ -49,16 +47,31 @@ class ThreadPoolTaskGroup;
49
47
// / available threads are used up by tasks waiting for a task that has no thread
50
48
// / left to run on (this includes waiting on the returned future). It should be
51
49
// / generally safe to wait() for a group as long as groups do not form a cycle.
52
- class ThreadPool {
50
+ class ThreadPoolInterface {
51
+ // The actual method to enqueue a task to be defined by the concrete implementation.
52
+ virtual void asyncEnqueue (std::function<void ()> Task, ThreadPoolTaskGroup *Group) = 0;
53
+
53
54
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());
59
55
60
- // / Blocking destructor: the pool will wait for all the threads to complete.
61
- ~ThreadPool ();
56
+ // Destroying the pool will drain the pending tasks and wait. The current thread may
57
+ // participate in the execution of the pending tasks.
58
+ virtual ~ThreadPoolInterface ();
59
+
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;
74
+
62
75
63
76
// / Asynchronous submission of a task to the pool. The returned future can be
64
77
// / used to wait for the task to finish and is *non-blocking* on destruction.
@@ -92,27 +105,33 @@ class ThreadPool {
92
105
&Group);
93
106
}
94
107
95
- // / Blocking wait for all the threads to complete and the queue to be empty.
96
- // / It is an error to try to add new tasks while blocking on this call.
97
- // / Calling wait() from a task would deadlock waiting for itself.
98
- void wait ();
108
+ private:
99
109
100
- // / Blocking wait for only all the threads in the given group to complete.
101
- // / It is possible to wait even inside a task, but waiting (directly or
102
- // / indirectly) on itself will deadlock. If called from a task running on a
103
- // / worker thread, the call may process pending tasks while waiting in order
104
- // / not to waste the thread.
105
- void wait (ThreadPoolTaskGroup &Group);
110
+ // / Asynchronous submission of a task to the pool. The returned future can be
111
+ // / used to wait for the task to finish and is *non-blocking* on destruction.
112
+ template <typename ResTy>
113
+ std::shared_future<ResTy> asyncImpl (std::function<ResTy()> Task,
114
+ ThreadPoolTaskGroup *Group) {
106
115
107
- // TODO: misleading legacy name warning!
108
- // Returns the maximum number of worker threads in the pool, not the current
109
- // number of threads!
110
- unsigned getThreadCount () const { return MaxThreadCount; }
116
+ # if LLVM_ENABLE_THREADS
117
+ // / Wrap the Task in a std::function<void()> that sets the result of the
118
+ // / corresponding future.
119
+ auto R = createTaskAndFuture (Task);
111
120
112
- // / Returns true if the current thread is a worker thread of this thread pool.
113
- bool isWorkerThread () const ;
121
+ asyncEnqueue (std::move (R.first ), Group);
122
+ return R.second .share ();
123
+
124
+ #else // LLVM_ENABLE_THREADS Disabled
125
+
126
+ // Get a Future with launch::deferred execution using std::async
127
+ auto Future = std::async (std::launch::deferred, std::move (Task)).share ();
128
+ // Wrap the future so that both ThreadPool::wait() can operate and the
129
+ // returned future can be sync'ed on.
130
+ Tasks.emplace_back (std::make_pair ([Future]() { Future.get (); }, Group));
131
+ return Future;
132
+ #endif
133
+ }
114
134
115
- private:
116
135
// / Helpers to create a promise and a callable wrapper of \p Task that sets
117
136
// / the result of the promise. Returns the callable and a future to access the
118
137
// / result.
@@ -137,44 +156,70 @@ class ThreadPool {
137
156
},
138
157
std::move (F)};
139
158
}
159
+ };
160
+
161
+ // / A ThreadPool implementation using std::threads.
162
+ // /
163
+ // / The pool keeps a vector of threads alive, waiting on a condition variable
164
+ // / for some work to become available.
165
+ class ThreadPool : public ThreadPoolInterface {
166
+ public:
167
+ // / Construct a pool using the hardware strategy \p S for mapping hardware
168
+ // / execution resources (threads, cores, CPUs)
169
+ // / Defaults to using the maximum execution resources in the system, but
170
+ // / accounting for the affinity mask.
171
+ ThreadPool (ThreadPoolStrategy S = hardware_concurrency());
172
+
173
+ // / Blocking destructor: the pool will wait for all the threads to complete.
174
+ ~ThreadPool () override ;
175
+
176
+
177
+ // / Blocking wait for all the threads to complete and the queue to be empty.
178
+ // / It is an error to try to add new tasks while blocking on this call.
179
+ // / Calling wait() from a task would deadlock waiting for itself.
180
+ void wait () override ;
181
+
182
+ // / Blocking wait for only all the threads in the given group to complete.
183
+ // / It is possible to wait even inside a task, but waiting (directly or
184
+ // / indirectly) on itself will deadlock. If called from a task running on a
185
+ // / worker thread, the call may process pending tasks while waiting in order
186
+ // / not to waste the thread.
187
+ void wait (ThreadPoolTaskGroup &Group) override ;
188
+
189
+ // TODO: misleading legacy name warning!
190
+ // Returns the maximum number of worker threads in the pool, not the current
191
+ // number of threads!
192
+ unsigned getThreadCount () const { return MaxThreadCount; }
193
+ unsigned getMaxConcurrency () const override { return MaxThreadCount; }
194
+
195
+
196
+ // / Returns true if the current thread is a worker thread of this thread pool.
197
+ bool isWorkerThread () const ;
198
+
199
+ private:
140
200
141
201
// / Returns true if all tasks in the given group have finished (nullptr means
142
202
// / all tasks regardless of their group). QueueLock must be locked.
143
203
bool workCompletedUnlocked (ThreadPoolTaskGroup *Group) const ;
144
204
205
+
145
206
// / Asynchronous submission of a task to the pool. The returned future can be
146
207
// / used to wait for the task to finish and is *non-blocking* on destruction.
147
- template <typename ResTy>
148
- std::shared_future<ResTy> asyncImpl (std::function<ResTy()> Task,
149
- ThreadPoolTaskGroup *Group) {
150
-
208
+ void asyncEnqueue (std::function<void ()> Task,
209
+ ThreadPoolTaskGroup *Group) override {
151
210
#if LLVM_ENABLE_THREADS
152
- // / Wrap the Task in a std::function<void()> that sets the result of the
153
- // / corresponding future.
154
- auto R = createTaskAndFuture (Task);
155
-
156
211
int requestedThreads;
157
212
{
158
213
// Lock the queue and push the new task
159
214
std::unique_lock<std::mutex> LockGuard (QueueLock);
160
215
161
216
// Don't allow enqueueing after disabling the pool
162
217
assert (EnableFlag && " Queuing a thread during ThreadPool destruction" );
163
- Tasks.emplace_back (std::make_pair (std::move (R. first ), Group));
218
+ Tasks.emplace_back (std::make_pair (std::move (Task ), Group));
164
219
requestedThreads = ActiveThreads + Tasks.size ();
165
220
}
166
221
QueueCondition.notify_one ();
167
222
grow (requestedThreads);
168
- return R.second .share ();
169
-
170
- #else // LLVM_ENABLE_THREADS Disabled
171
-
172
- // Get a Future with launch::deferred execution using std::async
173
- auto Future = std::async (std::launch::deferred, std::move (Task)).share ();
174
- // Wrap the future so that both ThreadPool::wait() can operate and the
175
- // returned future can be sync'ed on.
176
- Tasks.emplace_back (std::make_pair ([Future]() { Future.get (); }, Group));
177
- return Future;
178
223
#endif
179
224
}
180
225
@@ -224,7 +269,7 @@ class ThreadPool {
224
269
class ThreadPoolTaskGroup {
225
270
public:
226
271
// / The ThreadPool argument is the thread pool to forward calls to.
227
- ThreadPoolTaskGroup (ThreadPool &Pool) : Pool(Pool) {}
272
+ ThreadPoolTaskGroup (ThreadPoolInterface &Pool) : Pool(Pool) {}
228
273
229
274
// / Blocking destructor: will wait for all the tasks in the group to complete
230
275
// / by calling ThreadPool::wait().
@@ -241,7 +286,7 @@ class ThreadPoolTaskGroup {
241
286
void wait () { Pool.wait (*this ); }
242
287
243
288
private:
244
- ThreadPool &Pool;
289
+ ThreadPoolInterface &Pool;
245
290
};
246
291
247
292
} // namespace llvm
0 commit comments