diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 3f2ce9624..422af1900 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -61,6 +61,7 @@ ** - in a similar vein, the *filter operation* binds a predicate to decide about using or discarding data ** - in concert, expand- and transform operation allow to build hierarchy evaluation algorithms without exposing ** any knowledge regarding the concrete hierarchy used and explored as data source. + ** - further special convenience adaptors and _terminal functions_ are provided. ** ** In itself, the IterExplorer is an iterator with implementation defined type (all operations being inlined). ** But it is possible to package this structure behind a conventional iteration interface with virtual functions. @@ -1586,6 +1587,20 @@ namespace lib { return SRC {move(*this)}; } + /** + * _terminal builder_ to invoke a functor for side effect on the complete pipeline. + * @note exhausts and discards the pipeline itself + */ + template + void + foreach (FUN&& consumer) + { + auto consumeFun = iter_explorer::_FunTraits::adaptFunctor (forward (consumer)); + SRC& pipeline = *this; + for ( ; pipeline; ++pipeline) + consumeFun (pipeline); + } + /** _terminal builder_ to pour and materialise all results from this Pipeline. * @tparam CON a STL compliant container to store generated values (defaults to `vector`) * @return new instance of the target container, filled with all values diff --git a/src/lib/sync-barrier.hpp b/src/lib/sync-barrier.hpp new file mode 100644 index 000000000..4135abb36 --- /dev/null +++ b/src/lib/sync-barrier.hpp @@ -0,0 +1,85 @@ +/* + SYNC-BARRIER.hpp - N-fold synchronisation point with yield-wait + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file sync-barrier.hpp + ** A N-fold synchronisation latch using yield-wait until fulfilment. + ** Semantically, this barrier is similar to std::latch (introduced with C++20). + ** It is intended for one-time use and blocks any invocation until N invocations occurred. + ** The »wait« however is implemented as a spinning loop with `std::this_thread::yield`, + ** which places this implementation into a middle ground between a full-fledged blocking + ** lock and a busy spinning log. The `yield()` call is expected to de-prioritise the current + ** thread behind all other threads currently amenable for active execution, without actually + ** placing the thread into sleeping state. The coordination of the latch relies on Atomics. + ** @see SyncBarrier_test + ** @see steam::control::DispatcherLoop + ** @todo as of 9/2023 it remains to be seen if this facility is just a pre-C++20 workaround; + ** otherwise it may present distinct performance characteristics than std::latch, + ** possibly also a slightly more abstracted (and thus clearer) usage API. + */ + + +#ifndef LIB_SYNC_BARRIER_H +#define LIB_SYNC_BARRIER_H + + +#include "lib/error.hpp" +#include "lib/nocopy.hpp" +//#include "include/logging.h" +//#include "lib/meta/function.hpp" +//#include "lib/result.hpp" + +//#include +#include + + +namespace lib { + +// using lib::Literal; +// namespace error = lumiera::error; +// using error::LERR_(STATE); +// using error::LERR_(EXTERNAL); + + + + /** + * @todo write type comment + */ + class SyncBarrier + : util::NonCopyable + { + public: + explicit + SyncBarrier (size_t nFold =2) + { } + + void + sync() + { + UNIMPLEMENTED ("count-down latch logic"); + } + }; + + + +} // namespace lib +#endif /*LIB_SYNC_BARRIER_H*/ diff --git a/tests/library/sync-barrier-test.cpp b/tests/library/sync-barrier-test.cpp new file mode 100644 index 000000000..bf5fb3538 --- /dev/null +++ b/tests/library/sync-barrier-test.cpp @@ -0,0 +1,148 @@ +/* + SyncBarrier(Test) - verify yield-waiting synchronisation latch + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file sync-barrier-test.cpp + ** unit test \ref SyncBarrier_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" + +//#include "lib/thread.hpp" +#include "lib/sync-barrier.hpp" +#include "lib/iter-explorer.hpp" +#include "lib/util-foreach.hpp" +#include "lib/error.hpp" + +//#include +#include +#include +#include +#include + +using test::Test; +using util::and_all; +using lib::explore; +using std::atomic_uint; +using std::array; + +using std::this_thread::sleep_for; +using namespace std::chrono_literals; + + +namespace lib { +namespace test { + + namespace { + + const uint NUM_THREADS = 1024; + + atomic_uint stage1{0}; + atomic_uint stage2{0}; + atomic_uint finish{0}; + + SyncBarrier interThread{NUM_THREADS}; + SyncBarrier afterThread{NUM_THREADS+1}; + + /** + * A test thread to perform a summation protocol including synchronisation points + * - build a compound sum of random numbers in the first stage + * - wait for the compound sum to build up completely + * - book in the compound sum plus a further random number + */ + class TestThread + : std::thread ////////////////////////////////////////////////////////////////////OOO TOD-oh + { + public: + TestThread() + : thread{[&]() + { //-STAGE-1------------------------------ + localSum = rand() % 1000; // generate local value + stage1.fetch_add (localSum); // book in local value + interThread.sync(); // wait for all other threads to have booked in + + //-STAGE-2------------------------------ + localSum += stage1; // pick up compounded sum from STAGE-1 + localSum += rand() % 1000; // add further local value for STAGE-2 + stage2.fetch_add (localSum); // book in local sum + afterThread.sync(); // wait for other threads and supervisor + + finish.fetch_add(1); // mark completion of this thread + thread::detach(); //////////////////////////////////////////////OOO Wech-oh + }} + { } + + uint localSum; // *deliberately* not initialised to avoid race + bool isRunning() const { return thread::joinable(); } ///////////////////////OOO Wack-oh + }; + + + /** sum up all `localSum` fields from all TestThread instances in a container */ + template + uint + sumLocals (CON const& threads) + { + uint sum{0}; + explore (threads) + .foreach ([&](TestThread const& t){ sum += t.localSum; }); + return sum; + } + } + + + /*******************************************************************//** + * @test verify N-fold synchronisation points by multi-thread loat-test. + * + * @see lib::SyncBarrier + * @see steam::control::DispatcherLoop + */ + class SyncBarrier_test : public Test + { + + virtual void + run (Arg) + { + array threads; + + CHECK (0 == finish); + CHECK (and_all (threads, [](auto& t){ return t.isRunning(); })); + + afterThread.sync(); + sleep_for (1ms); // give the threads a chance to terminate + + CHECK (NUM_THREADS == finish); // all threads have passed out.... + CHECK (0 < stage1); + CHECK (stage1 < stage2); + CHECK (stage2 > sumLocals(threads)); + CHECK (stage2 == sumLocals(threads) + NUM_THREADS*stage1); // this holds only if all threads waited to get the complete stage1 sum + } + }; + + + + /** Register this test class... */ + LAUNCHER (SyncBarrier_test, "function common"); + + + +}} // namespace lib::test diff --git a/tests/library/thread-wrapper-self-recognition-test.cpp b/tests/library/thread-wrapper-self-recognition-test.cpp index 964c9d0e7..e8c578b49 100644 --- a/tests/library/thread-wrapper-self-recognition-test.cpp +++ b/tests/library/thread-wrapper-self-recognition-test.cpp @@ -20,6 +20,10 @@ * *****************************************************/ +/** @file thread-wrapper-self-recognition-test.cpp + ** unit test \ref ThreadWrapperSelfRecognitionTest_test + */ + #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" diff --git a/tests/library/util-foreach-test.cpp b/tests/library/util-foreach-test.cpp index 5be9fc175..624308fae 100644 --- a/tests/library/util-foreach-test.cpp +++ b/tests/library/util-foreach-test.cpp @@ -378,7 +378,7 @@ namespace test { CHECK (!and_all (coll, [] (uint elm) { return 1 < elm; })); CHECK ( has_any (coll, [] (uint elm) { return 0 < elm; })); - CHECK ( has_any (coll, [] (uint elm) { return elm >= NUM_ELMS; })); + CHECK ( has_any (coll, [] (uint elm) { return elm == NUM_ELMS; })); CHECK (!has_any (coll, [] (uint elm) { return elm > NUM_ELMS; })); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index d12c9d166..9af43b96d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -79616,6 +79616,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + @@ -79641,8 +79648,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
sonst könnte der Fall nicht zuverlässig getestet werden, daß der Test-Runner selber kurz nach dem Start stirbt

- - +
@@ -79663,42 +79669,73 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

ein Spinlock sollte man nur verwenden, wenn man sicher sein kann, daß man (a) fast nicht warten muß und (b) die CPU für sich hat. Andernfalls kann das schlimmstenfalls zum Verhungern des ganzen Systems führen...

- -
+
- - - +

Mutex + ConditionVar ist auch gar nicht so schlecht (aber noch schwergewichtiger)

- -
+
- - - +

Der Sprachstandard garantiert bereits, daß der Thread startklar ist, wenn der std::thread-Konstruktor verlassen wird. Allerdings kann es immer passieren, daß dummerweise der OS-Scheduler grade Anderes mit dieser CPU vor hat — und genau dann laufen wir in die Situation, vor der diese Sync-Barriere schützen soll; d.h. spätestens nach einer Schedule-Runde sollten alle Threads einmal vorbeigekommen sein.

- -
+ + +
+
+ + + + + + +

+ Wiederverwendung nach automatischem Reset nach Erfüllung ist in manchen Situationen verdammt tückisch; ohnehin stellt ein Latch stets eine Deadlock-Gefahr dar. +

+ +
+
+ + + + +

+ ...darauf läuft es nämlich hinaus; wenn wir tatsächlich auf C++20 sind, sollte die genaue API-Semantik und das Laufzeitverhalten bewertet werden, um zu entscheiden, ob beide Lösungen äquivalent sind... +

+ +
+
+ + + + +

+ Letztlich eine Entscheidung nach Bauchgefühl und ohne konkreten Anhaltspunkt; die Entscheidung kann erst überprüft werden, wenn es belastbare Einsatz-Szenarien gibt +

+ +
+ + +
+ + +
@@ -79715,16 +79752,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

solange sie noch auf den alten Code gehen, der den Threadpool voraussetzt (welcher aber für Library-Tests nicht zugelinkt wird)

- -
+ @@ -79748,16 +79782,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Denn wir betreiben hier nun keine System-Einrichtungen mehr, keinen Resource-Collector und keinen Threadpool. Es handelt sich lediglich um Adapter über der Std-Lib

- -
+
@@ -79771,6 +79802,57 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + +

+ Dieser Test ist zwar pfiffig geschrieben, aber er testet eigentlich den Sync/Monitor, nicht den Thread-Wrapper. In der aktuellen Form ist er sogar nahe an der Thema-Verfehlung, weil überhaupt nicht getestet wird,  daß mehrere Threads gestartet wurden und gelaufen sind +

+ +
+ +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
@@ -79786,6 +79868,41 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + + + + + + + +

+ Der Test setzt das Locking zwar erheblich unter Druck, aber er ist so komplex, daß man sich einarbeiten muß, um ihn überhaupt zu verstehen +

+ +
+
+ + + + + + + + + + + + + +
+