diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp new file mode 100644 index 000000000..2461c09ba --- /dev/null +++ b/src/lib/thread.hpp @@ -0,0 +1,299 @@ +/* + THREAD.hpp - thin convenience wrapper for starting threads + + Copyright (C) Lumiera.org + 2008, 2010 Hermann Vosseler + Christian Thaeter + + 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 thread.hpp + ** Convenience front-end for basic thread handling needs. + ** The Lumiera vault contains a dedicated low-level thread handling framework, + ** which is relevant for scheduling render activities to make best use of parallelisation + ** abilities of the given system. Typically, the upper layers should not have to deal much + ** with thread handling, yet at some point there is the need to implement a self contained + ** action running within a dedicated thread. The vault::Thread class is a wrapper to + ** represent such an parallel action conveniently and safely; together with the object + ** monitor, this allows to abstract away intricacies into self contained objects. + ** + ** @todo WIP 9/23 about to be replaced by a thin wrapper on top of C++17 threads ///////////////////////TICKET #1279 : consolidate to C++17 features + */ + + +#ifndef LIB_THREAD_H +#define LIB_THREAD_H + + +#include "lib/error.hpp" +#include "lib/nocopy.hpp" +#include "include/logging.h" +#include "lib/meta/function.hpp" +#include "lib/result.hpp" + +extern "C" { +#include "vault/threads.h" +} +//#include "vault/threadpool-init.hpp" + +#include +#include + + +namespace lib { + + using lib::Literal; + namespace error = lumiera::error; + using error::LERR_(STATE); + using error::LERR_(EXTERNAL); + + typedef struct nobug_flag* NoBugFlag; + + + + /************************************************************************//** + * A thin convenience wrapper for dealing with threads, + * as implemented by the threadpool in the vault (based on pthread). + * Using this wrapper... + * - helps with passing data to the function executed in the new thread + * - allows to bind to various kinds of functions including member functions + * The new thread starts immediately within the ctor; after returning, the new + * thread has already copied the arguments and indeed actively started to run. + * + * # Joining, cancellation and memory management + * In the basic version (class Thread), the created thread is completely detached + * and not further controllable. There is no way to find out its execution state, + * wait on termination or even cancel it. Client code needs to implement such + * facilities explicitly, if needed. Care has to be taken with memory management, + * as there are no guarantees beyond the existence of the arguments bound into + * the operation functor. If the operation in the started thread needs additional + * storage, it has to manage it actively. + * + * There is an extended version (class ThreadJoinable) to allow at least to wait + * on the started thread's termination (joining). Building on this it is possible + * to create a self-contained "thread in an object"; the dtor of such an class + * must join to prevent pulling away member variables the thread function will + * continue to use. + * + * # failures in the thread function + * The operation started in the new thread is protected by a top-level catch block. + * Error states or caught exceptions can be propagated through the lumiera_error + * state flag, when using ThreadJoinable::join(). By invoking `join().maybeThrow()` + * on a join-able thread, exceptions can be propagated. + * @note any errorstate or caught exception detected on termination of a standard + * async Thread is considered a violation of policy and will result in emergency + * shutdown of the whole application. + * + * # synchronisation barriers + * Lumiera threads provide a low-level synchronisation mechanism, which is used + * to secure the hand-over of additional arguments to the thread function. It + * can be used by client code, but care has to be taken to avoid getting out + * of sync. When invoking the #sync and #syncPoint functions, the caller will + * block until the counterpart has also invoked the corresponding function. + * If this doesn't happen, you'll block forever. + */ + class Thread + : util::MoveOnly + { + /** @internal perfect forwarding through a C-style `void*` */ + template + static FUN&& + forwardInitialiser (void* rawPtr) noexcept + { + REQUIRE (rawPtr); + FUN& initialiser = *reinterpret_cast (rawPtr); + return static_cast (initialiser); + } + + + template + static void + threadMain (void* arg) + { + using Fun= typename lib::meta::_Fun::Functor; + Fun _doIt_{forwardInitialiser (arg)}; + + //lumiera_thread_sync (); // sync point: arguments handed over /////////////////////////////////OOO TOD-oh + + try { + _doIt_(); // execute the actual operation in the new thread + } + + catch (std::exception& failure) + { + if (!lumiera_error_peek()) + LUMIERA_ERROR_SET (sync, STATE + ,failure.what()); + } + catch (...) + { + LUMIERA_ERROR_SET_ALERT (sync, EXTERNAL + , "Thread terminated abnormally"); + } + } + + + protected: + LumieraThread threadHandle_; + + /** @internal derived classes may create an inactive thread */ + Thread() : threadHandle_(0) { } + + + /** @internal use the Lumiera thread manager to start a new thread and hand over the operation */ + template + void + launchThread (Literal purpose, FUN&& operation, NoBugFlag logging_flag, uint additionalFlags =0) + { + REQUIRE (!lumiera_error(), "Error pending at thread start"); + using Functor = typename std::remove_reference::type; + threadHandle_ = + nullptr; ////////////////////////////////////////////////////////////////////////////////////OOO LaLaLa +// lumiera_thread_run ( LUMIERA_THREADCLASS_INTERACTIVE | additionalFlags +// , &threadMain +// , reinterpret_cast (&operation) +// , purpose.c() +// , logging_flag +// ); + if (!threadHandle_) + throw error::State ("Failed to start a new Thread for \"+purpose+\"" + , lumiera_error()); + + // make sure the new thread had the opportunity to take the Operation + // prior to leaving and thereby possibly destroying this local context + //lumiera_thread_sync_other (threadHandle_); //////////////////////////////////////////////////OOO Dadü DaDa + } + + + + public: + /** Create a new thread to execute the given operation. + * The new thread starts up synchronously, can't be cancelled and it can't be joined. + * @param purpose fixed char string used to denote the thread for diagnostics + * @param logging_flag NoBug flag to receive diagnostics regarding the new thread + * @param operation a functor holding the code to execute within the new thread. + * Any function-like entity with signature `void(void)` is acceptable. + * @warning The operation functor will be forwarded to create a copy residing + * on the stack of the new thread; thus it can be transient, however + * anything referred through a lambda closure here must stay alive + * until the new thread terminates. + */ + template + Thread (Literal purpose, FUN&& operation, NoBugFlag logging_flag = &NOBUG_FLAG(thread)) + : threadHandle_{nullptr} + { + launchThread (purpose, std::forward (operation), logging_flag); + } + + + /** @note by design there is no possibility to find out + * just based on the thread handle if some thread is alive. + * We define our own accounting here based on the internals + * of the thread wrapper. This will break down, if you mix + * uses of the C++ wrapper with the raw C functions. */ + bool + isValid() const + { + return threadHandle_; + } + + + /** Synchronisation barrier. In the function executing in this thread + * needs to be a corresponding Thread::syncPoint() call. Blocking until + * both the caller and the thread have reached the barrier. + */ + void + sync() + { + REQUIRE (isValid(), "Thread not running"); + if (!lumiera_thread_sync_other (threadHandle_)) + lumiera::throwOnError(); + } + + /** counterpart of the synchronisation barrier, to be called from + * within the thread to be synchronised. Will block until both + * this thread and the outward partner reached the barrier. + * @warning blocks on the _current_ thread's condition var + */ + static void + syncPoint () + { + lumiera_thread_sync (); + } + + protected: + /** determine if the currently executing code runs within this thread */ + bool + invokedWithinThread() const + { + REQUIRE (isValid(), "Thread not running"); + LumieraThread current = nullptr; // lumiera_thread_self (); /////////////////////////////////OOO + return current + and current == this->threadHandle_; + } + }; + + + + + + + /** + * Variant of the standard case, allowing additionally + * to join on the termination of this thread. + */ + class ThreadJoinable + : public Thread + { + public: + template + ThreadJoinable (Literal purpose, FUN&& operation, + NoBugFlag logging_flag = &NOBUG_FLAG(thread)) + : Thread{} + { + launchThread (purpose, std::forward (operation), logging_flag, + LUMIERA_THREAD_JOINABLE); + } + + + /** put the caller into a blocking wait until this thread has terminated. + * @return token signalling either success or failure. + * The caller can find out by invoking `isValid()` + * or `maybeThrow()` on this result token + */ + lib::Result + join () + { + if (!isValid()) + throw error::Logic ("joining on an already terminated thread"); + + lumiera_err errorInOtherThread = + "TODO TOD-oh";//lumiera_thread_join (threadHandle_); //////////////////////////////////OOO + threadHandle_ = 0; + + if (errorInOtherThread) + return error::State ("Thread terminated with error", errorInOtherThread); + else + return true; + } + }; + + + +} // namespace lib +#endif /*LIB_THREAD_H*/ diff --git a/src/vault/thread-wrapper.hpp b/src/vault/thread-wrapper.hpp index 21d7f6ffb..fc95284de 100644 --- a/src/vault/thread-wrapper.hpp +++ b/src/vault/thread-wrapper.hpp @@ -32,7 +32,7 @@ ** represent such an parallel action conveniently and safely; together with the object ** monitor, this allows to abstract away intricacies into self contained objects. ** - ** @note the thread wrapper is not intended for high performance computations. + ** @deprecated will be replaced by a thin wrapper on top of C++17 threads //////////////////////////////TICKET #1279 : consolidate to C++17 features */ @@ -106,6 +106,7 @@ namespace vault { * of sync. When invoking the #sync and #syncPoint functions, the caller will * block until the counterpart has also invoked the corresponding function. * If this doesn't happen, you'll block forever. + * @deprecated will be replaced by a thin wrapper on top of C++17 threads /////////////////////////////TICKET #1279 : consolidate to C++17 features */ class Thread : util::MoveOnly @@ -255,6 +256,7 @@ namespace vault { /** * Variant of the standard case, allowing additionally * to join on the termination of this thread. + * @deprecated will be replaced by a thin wrapper on top of C++17 threads /////////////////////////////TICKET #1279 : consolidate to C++17 features */ class ThreadJoinable : public Thread diff --git a/tests/11concurrency.tests b/tests/11concurrency.tests new file mode 100644 index 000000000..fcff233a7 --- /dev/null +++ b/tests/11concurrency.tests @@ -0,0 +1,44 @@ +TESTING "Library Test Suite: concurrency helpers" ./test-suite --group=common + + + +TEST "Multithread Locking by Monitor" SyncLocking_test < #include @@ -126,7 +126,7 @@ namespace test{ */ class HavocThread { - vault::ThreadJoinable thread_; + ThreadJoinable thread_; void doIt () diff --git a/tests/vault/sync-timedwait-test.cpp b/tests/library/sync-timedwait-test.cpp similarity index 100% rename from tests/vault/sync-timedwait-test.cpp rename to tests/library/sync-timedwait-test.cpp diff --git a/tests/vault/sync-waiting-test.cpp b/tests/library/sync-waiting-test.cpp similarity index 97% rename from tests/vault/sync-waiting-test.cpp rename to tests/library/sync-waiting-test.cpp index cd0cfc8bf..9fb2ee4fe 100644 --- a/tests/vault/sync-waiting-test.cpp +++ b/tests/library/sync-waiting-test.cpp @@ -28,7 +28,7 @@ #include "lib/test/run.hpp" #include "lib/error.hpp" -#include "vault/thread-wrapper.hpp" +#include "lib/thread.hpp" #include "lib/sync.hpp" #include @@ -156,7 +156,7 @@ namespace test{ void waitPingPong (Token& tok) { - typedef vault::ThreadJoinable Thread; + typedef ThreadJoinable Thread; //////////////////////////////////////WIP Thread ping ("SyncWaiting ping", bind (&Token::getIt, &tok)); Thread pong ("SyncWaiting pong", bind (&Token::getIt, &tok)); diff --git a/tests/vault/thread-wrapper-join-test.cpp b/tests/library/thread-wrapper-join-test.cpp similarity index 97% rename from tests/vault/thread-wrapper-join-test.cpp rename to tests/library/thread-wrapper-join-test.cpp index 0dc481a81..b451ed813 100644 --- a/tests/vault/thread-wrapper-join-test.cpp +++ b/tests/library/thread-wrapper-join-test.cpp @@ -28,7 +28,7 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "vault/thread-wrapper.hpp" +#include "lib/thread.hpp" #include "lib/error.hpp" #include @@ -38,7 +38,7 @@ using test::Test; -namespace vault { +namespace lib { namespace test { using lumiera::error::LUMIERA_ERROR_LOGIC; @@ -139,4 +139,4 @@ namespace test { -}} // namespace vault::test +}} // namespace lib::test diff --git a/tests/vault/thread-wrapper-self-recognition-test.cpp b/tests/library/thread-wrapper-self-recognition-test.cpp similarity index 96% rename from tests/vault/thread-wrapper-self-recognition-test.cpp rename to tests/library/thread-wrapper-self-recognition-test.cpp index d7931c585..964c9d0e7 100644 --- a/tests/vault/thread-wrapper-self-recognition-test.cpp +++ b/tests/library/thread-wrapper-self-recognition-test.cpp @@ -24,14 +24,14 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "vault/thread-wrapper.hpp" +#include "lib/thread.hpp" #include "lib/error.hpp" using test::Test; -namespace vault { +namespace lib { namespace test { namespace { @@ -86,4 +86,4 @@ namespace test { -}} // namespace vault::test +}} // namespace lib::test diff --git a/tests/vault/thread-wrapper-test.cpp b/tests/library/thread-wrapper-test.cpp similarity index 97% rename from tests/vault/thread-wrapper-test.cpp rename to tests/library/thread-wrapper-test.cpp index 9079cdb5a..9b7dcf2bc 100644 --- a/tests/vault/thread-wrapper-test.cpp +++ b/tests/library/thread-wrapper-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" -#include "vault/thread-wrapper.hpp" +#include "lib/thread.hpp" #include "lib/sync.hpp" #include "lib/symbol.hpp" @@ -37,7 +37,7 @@ using std::bind; using test::Test; -namespace vault { +namespace lib { namespace test { namespace { // private test classes and data... @@ -145,4 +145,4 @@ namespace vault { -}} // namespace vault::test +}} // namespace lib::test diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c8baf66f8..d12c9d166 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -79544,7 +79544,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -79559,38 +79559,38 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- +

- ...wenn es sich ohnehin nur um eine Einrichtung innerhalb einer bestimmten Applikation handelt, dann bleibt lediglich die Frage übrig, wohin der Quell/Objektcode gepackt wird + ...wenn es sich ohnehin nur um eine Einrichtung innerhalb einer bestimmten Applikation handelt, dann bleibt lediglich die Frage übrig, wohin der Quell/Objektcode gepackt wird — wenn man ein bestimmtes Feature braucht, kann man es implementieren (und die bestehende Lib-Implementierung kann das nicht verhindern)

- +

- Da Library-Funktionen ohne Weiteres genutzt werden, zwingt das dazu, statische Instanzen und versteckte magische Initialisierung zu verwenden + Da Library-Funktionen ohne Weiteres genutzt werden, verleiten solche eingebaute Hürden ehr dazu, schmutzige Workarounds zu machen

- +

- Sprachmittel sind stets aus gutem Grund da. Entweder es gibt aktuell adäquaten Nutzen, oder es gab früher mal einen relevanten Nutzen. Daher stellt es eine Anmaßung dar, wenn jemand nun einfach rundheraus behauptet, es sei falsch dieses Sprachmittel zu verwenden. Wenn man einen Anfänger ohne ausreichende Kompetenzen entlasten möchte, oder wenn man unter Einschränkung ein bestimmtes Nutmuster vorgeben möchte, dann baut man ein Framework, und keine Library. + Sprachmittel sind stets aus gutem Grund da. Entweder es gibt aktuell adäquaten Nutzen, oder es gab früher mal einen relevanten Nutzen. Daher stellt es eine Kompetenzüberschreitung dar, wenn jemand rundheraus behauptet, es sei falsch dieses Sprachmittel zu verwenden. Wenn man einen Anfänger ohne ausreichende Kompetenzen entlasten möchte, oder wenn man unter Einschränkung ein bestimmtes Nutmuster vorgeben möchte, dann baut man ein Framework, und keine Library.

- + @@ -79601,10 +79601,176 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
das sich nun auch im Standard als angemessen herausgebildet hat —

- aber die Standard-Lösung ist ausgereifter und verwendet moderne Sprachmittel + aber die Standard-Lösung ist ausgereifter und verwendet moderne Sprachmittel (Atomics)

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

+ sonst könnte der Fall nicht zuverlässig getestet werden, daß der Test-Runner selber kurz nach dem Start stirbt +

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

+ 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. +

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

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

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

+ 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 +

+ + +
+
+ + + + + + + + + + + +
@@ -79612,6 +79778,15 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + + +