From b3458485ba97a79b58e907b19837953b4f35d9e0 Mon Sep 17 00:00:00 2001 From: Drew Lewis Date: Sat, 6 Jun 2026 19:45:46 -0400 Subject: [PATCH] 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 --- include/sta/DispatchQueue.hh | 54 +++++++++++++++++++++++++++++++++--- util/DispatchQueue.cc | 20 ++++++------- 2 files changed, 60 insertions(+), 14 deletions(-) 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_);