1717
1818namespace threading {
1919 static size_t num_threads = 1 ;
20+
2021 static std::condition_variable wait_for_task;
2122 static std::mutex wait_for_task_mutex;
2223 static bool wait_for_task_condition;
23- static std::atomic_uint32_t wait_for_spindown_tasks;
24+
25+ static std::condition_variable wait_for_spindown_tasks;
26+ static std::mutex wait_for_spindown_task_mutex;
27+ static size_t wait_for_spindown_tasks_counter;
28+
29+ static std::condition_variable wait_for_spinup_tasks;
30+ static std::mutex wait_for_spinup_task_mutex;
31+ static size_t wait_for_spinup_tasks_counter;
32+
2433 static std::atomic<WorkerThreadTask> worker_task;
2534
2635 static SCP_vector<std::thread> worker_threads;
@@ -29,11 +38,21 @@ namespace threading {
2938 static void mp_worker_thread_main (size_t threadIdx) {
3039 while (true ) {
3140 {
32- // We're waiting for a new task, so spindown was successful
33- wait_for_spindown_tasks.fetch_add (1 , std::memory_order_release);
41+ std::scoped_lock lock {wait_for_spindown_task_mutex};
42+ ++wait_for_spindown_tasks_counter;
43+ wait_for_spindown_tasks.notify_all ();
44+ }
45+ // We're waiting for a new task, so spindown was successful
46+ {
3447 std::unique_lock<std::mutex> lk (wait_for_task_mutex);
3548 wait_for_task.wait (lk, []() { return wait_for_task_condition; });
3649 }
50+ // Notify that we passed the wait and can now start processing. This is necessary, as slow thread wakeups in very low workloads could cause the wait_for_task_condition to be false, never waking up the thread, locking on spindown wait
51+ {
52+ std::scoped_lock lock {wait_for_spinup_task_mutex};
53+ ++wait_for_spinup_tasks_counter;
54+ wait_for_spinup_tasks.notify_all ();
55+ }
3756
3857 switch (worker_task.load (std::memory_order_acquire)) {
3958 case WorkerThreadTask::EXIT :
@@ -148,7 +167,11 @@ namespace threading {
148167 // External Functions
149168
150169 void spin_up_threaded_task (WorkerThreadTask task) {
151- wait_for_spindown_tasks.store (0 , std::memory_order_release);
170+ {
171+ std::scoped_lock lock {wait_for_spindown_task_mutex};
172+ wait_for_spindown_tasks_counter = 0 ;
173+ // No notify here cause we only ever lock, never unlock the wait here.
174+ }
152175 worker_task.store (task);
153176 {
154177 std::scoped_lock lock {wait_for_task_mutex};
@@ -158,13 +181,24 @@ namespace threading {
158181 }
159182
160183 void spin_down_threaded_task () {
161- std::scoped_lock lock {wait_for_task_mutex};
162- wait_for_task_condition = false ;
184+ // Make sure all threads are actually running before stopping the pool again
185+ {
186+ std::unique_lock<std::mutex> lk (wait_for_spinup_task_mutex);
187+ wait_for_spinup_tasks.wait (lk, []() { return wait_for_spinup_tasks_counter >= num_threads; });
188+ wait_for_spinup_tasks_counter = 0 ;
189+ }
190+ {
191+ std::scoped_lock lock {wait_for_task_mutex};
192+ wait_for_task_condition = false ;
193+ }
163194 }
164195
165196 void spin_down_wait_complete () {
166197 // Technically, spindowns should only occur when the actual code is confirmed to be complete. So busy-waiting here is not an issue.
167- while (wait_for_spindown_tasks.load (std::memory_order_acquire) < num_threads);
198+ {
199+ std::unique_lock<std::mutex> lk (wait_for_spindown_task_mutex);
200+ wait_for_spindown_tasks.wait (lk, []() { return wait_for_spindown_tasks_counter >= num_threads; });
201+ };
168202 }
169203
170204 void init_task_pool () {
0 commit comments