OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-cogutil commits / 951f626

Avoid busy spinloops, use c++20 atomic::wait
951f626a0c2ba0fe95cba90a41ca44cdf5924192
by Linas Vepstas <linasvepstas@gmail.com> · 2025-12-07T02:13:02.000Z

Changed files (2) +34 -27

opencog/util/async_buffer.h +17 -14
……@@ -30,7 +30,6 @@
3030#include <mutex>
3131#include <thread>
3232#include <vector>
33#include <unistd.h> /* for usleep() */
3433
3534#include <opencog/util/concurrent_set.h>
3635#include <opencog/util/exceptions.h>
……@@ -120,11 +119,6 @@ namespace opencog
120119 * barrier() call needs to do is to be a fence, ensuring that everything
121120 * before really is before everything after. It didn't need to actually
122121 * drain everything.
123 *
124 * Another issue is that the assorted loops to drain queues use
125 * spin-loops and usleeps. I think this is mostly harmless, but is
126 * impure, and should be replaced by CV's. Doing so becomes much easier
127 * once C++20 is widely available, as it has CV's on std::atomic ints.
128122 */
129123template<typename Writer, typename Element>
130124class async_buffer
……@@ -339,11 +333,11 @@ void async_buffer<Writer, Element>::stop_writer_threads()
339333
340334 _stopping_writers = true;
341335
342 // Spin a while, until the writer threads are (mostly) done.
336 // Wait until the writer threads are (mostly) done.
343337 while (0 < _pending)
344338 {
345 // std::this_thread::sleep_for(std::chrono::milliseconds(1));
346 usleep(1000);
339 unsigned long pend = _pending.load();
340 if (pend > 0) _pending.wait(pend);
347341 }
348342
349343 // Now tell all the threads that they are done.
……@@ -385,10 +379,12 @@ void async_buffer<Writer, Element>::drain()
385379 _stall_writers = false;
386380 _flush_count++;
387381
388 // XXX TODO - when C++20 becomes widely available,
389 // replace this loop by _pending.wait(0)
382 // Wait for all pending work to complete
390383 while (0 < _pending)
391 std::this_thread::sleep_for(std::chrono::milliseconds(1));
384 {
385 unsigned long pend = _pending.load();
386 if (pend > 0) _pending.wait(pend);
387 }
392388
393389 _stall_writers = save_stall;
394390}
……@@ -468,7 +464,10 @@ void async_buffer<Writer, Element>::barrier(const Element& elt)
468464
469465 // Wait for all threads to complete their barrier work
470466 while (_barrier_remaining.load() > 0)
471 std::this_thread::sleep_for(std::chrono::milliseconds(1));
467 {
468 unsigned rem = _barrier_remaining.load();
469 if (rem > 0) _barrier_remaining.wait(rem);
470 }
472471}
473472
474473/// A single write thread. Reads elements from set, and invokes the
……@@ -490,7 +489,9 @@ void async_buffer<Writer, Element>::write_loop()
490489 while (_barrier_remaining.load() > 0 and
491490 my_last_phase >= _barrier_phase.load())
492491 {
493 std::this_thread::sleep_for(std::chrono::milliseconds(1));
492 unsigned rem = _barrier_remaining.load();
493 if (rem > 0 and my_last_phase >= _barrier_phase.load())
494 _barrier_remaining.wait(rem);
494495 }
495496
496497 // Do nothing, if asked to stall.
……@@ -509,6 +510,7 @@ void async_buffer<Writer, Element>::write_loop()
509510 my_last_phase = current_phase;
510511 (_writer->*_do_write)(elt);
511512 _barrier_remaining.fetch_sub(1);
513 _barrier_remaining.notify_all();
512514 }
513515 else
514516 {
……@@ -518,6 +520,7 @@ void async_buffer<Writer, Element>::write_loop()
518520
519521 _busy_writers --;
520522 _pending --;
523 _pending.notify_all();
521524 }
522525 }
523526 catch (typename concurrent_set<Element>::Canceled& e)
opencog/util/async_method_caller.h +17 -13
……@@ -94,11 +94,6 @@ namespace opencog
9494 * barrier() call needs to do is to be a fence, ensuring that everything
9595 * before really is before everything after. It didn't need to actually
9696 * drain everything.
97 *
98 * Another issue is that the assorted loops to drain queues use
99 * spin-loops and usleeps. I think this is mostly harmless, but is
100 * impure, and should be replaced by CV's. Doing so becomes much easier
101 * once C++20 is widely available, as it has CV's on std::atomic ints.
10297 */
10398template<typename Writer, typename Element>
10499class async_caller
……@@ -258,11 +253,11 @@ void async_caller<Writer, Element>::stop_writer_threads()
258253
259254 _stopping_writers = true;
260255
261 // Spin a while, until the writer threads are (mostly) done.
256 // Wait until the writer threads are (mostly) done.
262257 while (0 < _pending)
263258 {
264 std::this_thread::sleep_for(std::chrono::milliseconds(1));
265 // usleep(1000);
259 unsigned long pend = _pending.load();
260 if (pend > 0) _pending.wait(pend);
266261 }
267262
268263 // Now tell all the threads that they are done.
……@@ -302,10 +297,12 @@ void async_caller<Writer, Element>::drain()
302297{
303298 _flush_count++;
304299
305 // XXX TODO - when C++20 becomes widely available,
306 // replace this loop by _pending.wait(0)
300 // Wait for all pending work to complete
307301 while (0 < _pending)
308 std::this_thread::sleep_for(std::chrono::milliseconds(1));
302 {
303 unsigned long pend = _pending.load();
304 if (pend > 0) _pending.wait(pend);
305 }
309306}
310307
311308
……@@ -385,7 +382,10 @@ void async_caller<Writer, Element>::barrier(const Element& elt)
385382
386383 // Wait for all threads to complete their barrier work
387384 while (_barrier_remaining.load() > 0)
388 std::this_thread::sleep_for(std::chrono::milliseconds(1));
385 {
386 unsigned rem = _barrier_remaining.load();
387 if (rem > 0) _barrier_remaining.wait(rem);
388 }
389389}
390390
391391/// A single write thread. Reads elements from queue, and invokes the
……@@ -407,7 +407,9 @@ void async_caller<Writer, Element>::write_loop()
407407 while (_barrier_remaining.load() > 0 and
408408 my_last_phase >= _barrier_phase.load())
409409 {
410 std::this_thread::sleep_for(std::chrono::milliseconds(1));
410 unsigned rem = _barrier_remaining.load();
411 if (rem > 0 and my_last_phase >= _barrier_phase.load())
412 _barrier_remaining.wait(rem);
411413 }
412414
413415 Element elt = _store_queue.value_pop();
……@@ -420,6 +422,7 @@ void async_caller<Writer, Element>::write_loop()
420422 my_last_phase = current_phase;
421423 (_writer->*_do_write)(elt);
422424 _barrier_remaining.fetch_sub(1);
425 _barrier_remaining.notify_all();
423426 }
424427 else
425428 {
……@@ -429,6 +432,7 @@ void async_caller<Writer, Element>::write_loop()
429432
430433 _busy_writers --;
431434 _pending --;
435 _pending.notify_all();
432436 }
433437 }
434438 catch (typename concurrent_queue<Element>::Canceled& e)