diff --git a/include/sta/DispatchQueue.hh b/include/sta/DispatchQueue.hh index ceda2e78..e30f9d70 100644 --- a/include/sta/DispatchQueue.hh +++ b/include/sta/DispatchQueue.hh @@ -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 +#include #include #include #include @@ -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 count_{0}; +}; + class DispatchQueue { using fp_t = std::function; @@ -45,7 +91,7 @@ private: std::vector threads_; std::queue q_; std::condition_variable cv_; - std::atomic pending_task_count_; + DynamicLatch pending_task_count_latch_; bool quit_ = false; }; diff --git a/util/DispatchQueue.cc b/util/DispatchQueue.cc index 5347a07a..c1cd11f7 100644 --- a/util/DispatchQueue.cc +++ b/util/DispatchQueue.cc @@ -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 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 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_);