Dont spin main for queue (#443)
* Use DynamicLatch in DispatchQueue to avoid main thread spinning Replace the busy-yielding pending_task_count_ loop in DispatchQueue::finishTasks with a blocking DynamicLatch. This avoids having the main thread consume CPU cycles while waiting for dispatched tasks to complete. The DynamicLatch implementation uses C++20 std::atomic::wait/notify_all for efficient blocking and wakeup, with proper release-acquire semantics to ensure task results are visible to the waiting thread. * Reformat DynamicLatch to match DispatchQueue style * Update attribution headers in DispatchQueue files to note modifications
This commit is contained in:
parent
ffe126af2a
commit
b3458485ba
|
|
@ -1,11 +1,14 @@
|
|||
// Author Phillip Johnston
|
||||
// Original Author: Phillip Johnston
|
||||
// Licensed under CC0 1.0 Universal
|
||||
// https://github.com/embeddedartistry/embedded-resources/blob/master/examples/cpp/dispatch.cpp
|
||||
// https://embeddedartistry.com/blog/2017/2/1/dispatch-queues?rq=dispatch
|
||||
// Original source: https://github.com/embeddedartistry/embedded-resources/blob/master/examples/cpp/dispatch.cpp
|
||||
// Original article: https://embeddedartistry.com/blog/2017/2/1/dispatch-queues?rq=dispatch
|
||||
//
|
||||
// Modified for OpenSTA to use C++20 non-spinning DynamicLatch for synchronization.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
|
@ -16,6 +19,49 @@
|
|||
|
||||
namespace sta {
|
||||
|
||||
class DynamicLatch
|
||||
{
|
||||
public:
|
||||
explicit DynamicLatch(std::ptrdiff_t initial_count = 0) :
|
||||
count_(initial_count)
|
||||
{
|
||||
}
|
||||
|
||||
// Delete copy/move constructors to prevent accidental slicing/copying of atomics
|
||||
DynamicLatch(const DynamicLatch&) = delete;
|
||||
DynamicLatch& operator=(const DynamicLatch&) = delete;
|
||||
|
||||
// Increases the latch count (used when a new task is dispatched)
|
||||
void
|
||||
countUp()
|
||||
{
|
||||
count_.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Decreases the latch count and wakes waiting threads if it hits zero
|
||||
void
|
||||
countDown(std::ptrdiff_t n = 1)
|
||||
{
|
||||
if (count_.fetch_sub(n, std::memory_order_release) == n) {
|
||||
count_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
// Blocks until the count reaches zero
|
||||
void
|
||||
wait() const
|
||||
{
|
||||
std::ptrdiff_t current = count_.load(std::memory_order_acquire);
|
||||
while (current != 0) {
|
||||
count_.wait(current, std::memory_order_acquire);
|
||||
current = count_.load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::atomic<std::ptrdiff_t> count_{0};
|
||||
};
|
||||
|
||||
class DispatchQueue
|
||||
{
|
||||
using fp_t = std::function<void(int thread)>;
|
||||
|
|
@ -45,7 +91,7 @@ private:
|
|||
std::vector<std::thread> threads_;
|
||||
std::queue<fp_t> q_;
|
||||
std::condition_variable cv_;
|
||||
std::atomic<size_t> pending_task_count_;
|
||||
DynamicLatch pending_task_count_latch_;
|
||||
bool quit_ = false;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
// Author Phillip Johnston
|
||||
// Original Author: Phillip Johnston
|
||||
// Licensed under CC0 1.0 Universal
|
||||
// https://github.com/embeddedartistry/embedded-resources/blob/master/examples/cpp/dispatch.cpp
|
||||
// https://embeddedartistry.com/blog/2017/2/1/dispatch-queues?rq=dispatch
|
||||
// Original source: https://github.com/embeddedartistry/embedded-resources/blob/master/examples/cpp/dispatch.cpp
|
||||
// Original article: https://embeddedartistry.com/blog/2017/2/1/dispatch-queues?rq=dispatch
|
||||
//
|
||||
// Modified for OpenSTA to use C++20 non-spinning DynamicLatch for synchronization.
|
||||
|
||||
#include "DispatchQueue.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
DispatchQueue::DispatchQueue(size_t thread_count) :
|
||||
threads_(thread_count),
|
||||
pending_task_count_(0)
|
||||
threads_(thread_count)
|
||||
{
|
||||
for(size_t i = 0; i < thread_count; i++)
|
||||
threads_[i] = std::thread(&DispatchQueue::dispatch_thread_handler, this, i);
|
||||
|
|
@ -58,8 +59,7 @@ DispatchQueue::getThreadCount() const
|
|||
void
|
||||
DispatchQueue::finishTasks()
|
||||
{
|
||||
while (pending_task_count_.load(std::memory_order_acquire) != 0)
|
||||
std::this_thread::yield();
|
||||
pending_task_count_latch_.wait();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -67,7 +67,7 @@ DispatchQueue::dispatch(const fp_t& op)
|
|||
{
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
q_.push(op);
|
||||
pending_task_count_++;
|
||||
pending_task_count_latch_.countUp();
|
||||
|
||||
// Manual unlocking is done before notifying, to avoid waking up
|
||||
// the waiting thread only to block again (see notify_one for details)
|
||||
|
|
@ -80,7 +80,7 @@ DispatchQueue::dispatch(fp_t&& op)
|
|||
{
|
||||
std::unique_lock<std::mutex> lock(lock_);
|
||||
q_.push(std::move(op));
|
||||
pending_task_count_++;
|
||||
pending_task_count_latch_.countUp();
|
||||
|
||||
// Manual unlocking is done before notifying, to avoid waking up
|
||||
// the waiting thread only to block again (see notify_one for details)
|
||||
|
|
@ -106,7 +106,7 @@ DispatchQueue::dispatch_thread_handler(size_t i)
|
|||
|
||||
op(i);
|
||||
|
||||
pending_task_count_--;
|
||||
pending_task_count_latch_.countDown();
|
||||
lock.lock();
|
||||
}
|
||||
} while (!quit_);
|
||||
|
|
|
|||
Loading…
Reference in New Issue