[Support] Simplify and optimize ThreadPool

* Merge QueueLock and CompletionLock.
* Avoid spurious CompletionCondition.notify_all() when ActiveThreads is greater than 0.
* Use default member initializers.

Reviewed By: mehdi_amini

Differential Revision: https://reviews.llvm.org/D78856
This commit is contained in:
Fangrui Song 2020-04-24 20:50:06 -07:00
parent 03ffe58605
commit 6f23049119
2 changed files with 17 additions and 21 deletions

View File

@ -72,6 +72,8 @@ public:
unsigned getThreadCount() const { return ThreadCount; } unsigned getThreadCount() const { return ThreadCount; }
private: private:
bool workCompletedUnlocked() { return !ActiveThreads && Tasks.empty(); }
/// Asynchronous submission of a task to the pool. The returned future can be /// Asynchronous submission of a task to the pool. The returned future can be
/// used to wait for the task to finish and is *non-blocking* on destruction. /// used to wait for the task to finish and is *non-blocking* on destruction.
std::shared_future<void> asyncImpl(TaskTy F); std::shared_future<void> asyncImpl(TaskTy F);
@ -86,16 +88,15 @@ private:
std::mutex QueueLock; std::mutex QueueLock;
std::condition_variable QueueCondition; std::condition_variable QueueCondition;
/// Locking and signaling for job completion /// Signaling for job completion
std::mutex CompletionLock;
std::condition_variable CompletionCondition; std::condition_variable CompletionCondition;
/// Keep track of the number of thread actually busy /// Keep track of the number of thread actually busy
std::atomic<unsigned> ActiveThreads; unsigned ActiveThreads = 0;
#if LLVM_ENABLE_THREADS // avoids warning for unused variable #if LLVM_ENABLE_THREADS // avoids warning for unused variable
/// Signal for the destruction of the pool, asking thread to exit. /// Signal for the destruction of the pool, asking thread to exit.
bool EnableFlag; bool EnableFlag = true;
#endif #endif
unsigned ThreadCount; unsigned ThreadCount;

View File

@ -21,8 +21,7 @@ using namespace llvm;
#if LLVM_ENABLE_THREADS #if LLVM_ENABLE_THREADS
ThreadPool::ThreadPool(ThreadPoolStrategy S) ThreadPool::ThreadPool(ThreadPoolStrategy S)
: ActiveThreads(0), EnableFlag(true), : ThreadCount(S.compute_thread_count()) {
ThreadCount(S.compute_thread_count()) {
// Create ThreadCount threads that will loop forever, wait on QueueCondition // Create ThreadCount threads that will loop forever, wait on QueueCondition
// for tasks to be queued or the Pool to be destroyed. // for tasks to be queued or the Pool to be destroyed.
Threads.reserve(ThreadCount); Threads.reserve(ThreadCount);
@ -44,24 +43,24 @@ ThreadPool::ThreadPool(ThreadPoolStrategy S)
// We first need to signal that we are active before popping the queue // We first need to signal that we are active before popping the queue
// in order for wait() to properly detect that even if the queue is // in order for wait() to properly detect that even if the queue is
// empty, there is still a task in flight. // empty, there is still a task in flight.
{ ++ActiveThreads;
std::unique_lock<std::mutex> LockGuard(CompletionLock);
++ActiveThreads;
}
Task = std::move(Tasks.front()); Task = std::move(Tasks.front());
Tasks.pop(); Tasks.pop();
} }
// Run the task we just grabbed // Run the task we just grabbed
Task(); Task();
bool Notify;
{ {
// Adjust `ActiveThreads`, in case someone waits on ThreadPool::wait() // Adjust `ActiveThreads`, in case someone waits on ThreadPool::wait()
std::unique_lock<std::mutex> LockGuard(CompletionLock); std::lock_guard<std::mutex> LockGuard(QueueLock);
--ActiveThreads; --ActiveThreads;
Notify = workCompletedUnlocked();
} }
// Notify task completion if this is the last active thread, in case
// Notify task completion, in case someone waits on ThreadPool::wait() // someone waits on ThreadPool::wait().
CompletionCondition.notify_all(); if (Notify)
CompletionCondition.notify_all();
} }
}); });
} }
@ -69,12 +68,8 @@ ThreadPool::ThreadPool(ThreadPoolStrategy S)
void ThreadPool::wait() { void ThreadPool::wait() {
// Wait for all threads to complete and the queue to be empty // Wait for all threads to complete and the queue to be empty
std::unique_lock<std::mutex> LockGuard(CompletionLock); std::unique_lock<std::mutex> LockGuard(QueueLock);
// The order of the checks for ActiveThreads and Tasks.empty() matters because CompletionCondition.wait(LockGuard, [&] { return workCompletedUnlocked(); });
// any active threads might be modifying the Tasks queue, and this would be a
// race.
CompletionCondition.wait(LockGuard,
[&] { return !ActiveThreads && Tasks.empty(); });
} }
std::shared_future<void> ThreadPool::asyncImpl(TaskTy Task) { std::shared_future<void> ThreadPool::asyncImpl(TaskTy Task) {
@ -109,7 +104,7 @@ ThreadPool::~ThreadPool() {
// No threads are launched, issue a warning if ThreadCount is not 0 // No threads are launched, issue a warning if ThreadCount is not 0
ThreadPool::ThreadPool(ThreadPoolStrategy S) ThreadPool::ThreadPool(ThreadPoolStrategy S)
: ActiveThreads(0), ThreadCount(S.compute_thread_count()) { : ThreadCount(S.compute_thread_count()) {
if (ThreadCount != 1) { if (ThreadCount != 1) {
errs() << "Warning: request a ThreadPool with " << ThreadCount errs() << "Warning: request a ThreadPool with " << ThreadCount
<< " threads, but LLVM_ENABLE_THREADS has been turned off\n"; << " threads, but LLVM_ENABLE_THREADS has been turned off\n";