Lumiera
0.pre.03
»edit your freedom«
|
Go to the source code of this file.
Convenience front-end to simplify and codify basic thread handling.
While the implementation of threading and concurrency support is based on the C++ standard library, using in-project wrappers as front-end allows to codify some references and provide simplifications for the prevalent use case. Notably, threads which must be joined are qualified as special case, while the standard case will just detach()
at thread end. The main-level of each thread catches exceptions, which are typically ignored to keep the application running. Moreover, similar convenience wrappers are provided to implement N-fold synchronisation and to organise global locking and waiting in accordance with the Object Monitor pattern. In concert, these allow to package concurrency facilities into self-contained RAII-style objects.
Based on experience, there seem to be two fundamentally different usage patterns for thread-like entities: In most cases, they are just launched to participate in interactions elsewhere defined. However, sometimes dedicated sub-processing is established and supervised, finally to join results. And while the underlying implementation supports both usage styles, a decision was made to reflect this dichotomy by casting two largely distinct front-ends.
The »just launch it« scheme is considered the default and embodied into lib::Thread. Immediately launched on construction using the given Invokable Functor and binding arguments, such a thread is not meant to be managed further, beyond possibly detecting the live-ness state through bool
-check. Exceptions propagating to top level within the new thread will be coughed and ignored, terminating and discarding the thread. Note however, since especially derived classes can be used to create a safe anchor and working space for the launched operations, it must be avoided to destroy the Thread object while still operational; as a matter of design, it should be assured the instance object outlives the enclosed chain of activity. As a convenience, the destructor blocks for a short timespan of 20ms; a thread running beyond that grace period will kill the whole application by std::terminate
.
For the exceptional case when a supervising thread need to await the termination of launched threads, a different front-end lib::ThreadJoinable is provided, exposing the join()
operation. This operation returns a »Either« wrapper, to transport the return value and possible exceptions from the thread function to the caller. Such threads must be joined however, and thus the destructor immediately terminates the application in case the thread is still running.
A further variant ThreadHookable allows to attach user-provided callbacks invoked from the thread lifecycle; this can be used to build a thread-object that manages itself autonomously, or a thread that opens / closes interfaces tied to its lifecycle.
The C++ standard provides that the end of the std::thread
constructor syncs-with the start of the new thread function, and likewise the end of the thread activity syncs-with the return from join()
. According to the [syncs-with definition], this implies the happens before relation and thus precludes a data race. In practice thus
join()
is guaranteed to see all effects of the terminated thread. Note however, that these guarantees do not extend into the initialisations performed in a derived class's constructor, which start only after leaving the ctor of Thread. So in theory there is a possible race between the extended setup in derived classes, and the use of these facilities from within the thread function. In practice the new thread, while already marked as live, still must be scheduled by the OS to commence, which does not completely remove the possibility of undefined behaviour however. So in cases where a race could be critical, additional means must be implemented; a possible solution would be to use a N-fold synchronisation barrier explicitly, or otherwise to ensure there is sufficient delay in the starting thread function.While these thread-wrapper building blocks aim at packaging the complexity away, there is the danger to miss a potential race, which is inherent with starting threads: the operation in the new thread contends with any initialisation done after launching the thread. Even though encapsulating complex concurrent logic into an opaque component, as built on top of the thread-wrappers, is highly desirable from a code sanity angle — it is dangerously tempting to package self-contained data initialisation into a subclass, leading to the kind of undefined behaviour, which „can never happen“ under normal circumstances. Even while the OS scheduler typically adds an latency of at least 100µs to the start of the new thread function, initialising anything (even subclass data members) after creating the thread-wrapper instance is undefined behaviour. As a remedy
[syncs-with definition] : https://en.cppreference.com/w/cpp/atomic/memory_order#Synchronizes_with
Definition in file thread.hpp.
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "include/logging.h"
#include "lib/meta/trait.hpp"
#include "lib/meta/function.hpp"
#include "lib/format-util.hpp"
#include "lib/result.hpp"
#include <utility>
#include <thread>
#include <string>
#include <tuple>
Classes | |
struct | ThreadLifecycle< POL, RES >::Launch |
Configuration builder to define the operation running within the thread, and possibly configure further details, depending on the actual Policy used. More... | |
struct | PolicyLaunchOnly< BAS, typename > |
Thread Lifecycle Policy: More... | |
struct | PolicyLifecycleHook< BAS, TAR > |
Thread Lifecycle Policy Extension: invoke user-provided callbacks from within thread lifecycle. More... | |
struct | PolicyResultJoin< BAS, RES > |
Thread Lifecycle Policy: More... | |
class | Thread |
A thin convenience wrapper to simplify thread-handling. More... | |
class | ThreadHookable |
Extended variant of the standard case, allowing to install callbacks (hook functions) to be invoked during thread lifecycle: More... | |
class | ThreadJoinable< RES > |
Variant of the standard case, requiring to wait and join() on the termination of this thread. More... | |
class | ThreadLifecycle< POL, RES > |
Policy-based configuration of thread lifecycle. More... | |
struct | ThreadWrapper |
Functions | |
template<class TAR = ThreadHookable> | |
void | launchDetached (ThreadHookable::Launch &&launchBuilder) |
Launch an autonomous self-managing thread (and forget about it). More... | |
template<class TAR = ThreadHookable, typename... INVO> | |
void | launchDetached (string const &threadID, INVO &&...args) |
Launch an autonomous self-managing thread (and forget about it). More... | |
template<class TAR , typename... ARGS> | |
void | launchDetached (string const &threadID, void(TAR::*memFun)(ARGS...), ARGS ...args) |
Special variant bind a member function of the subclass into the autonomous thread. | |
template<class TAR , typename... ARGS> | |
void | launchDetached (void(TAR::*memFun)(ARGS...), ARGS ...args) |
Special variant without explicitly given thread-ID. | |
std::string | sanitise (string const &org) |
produce an identifier based on the given string. More... | |
template<typename FUN , typename... ARGS> | |
ThreadJoinable (string const &, FUN &&, ARGS &&...) -> ThreadJoinable< std::invoke_result_t< FUN, ARGS... >> | |
deduction guide: find out about result value to capture from a generic callable. More... | |
Namespaces | |
lib | |
Implementation namespace for support and library code. | |
string sanitise | ( | string const & | org | ) |
produce an identifier based on the given string.
remove non-standard-chars, reduce sequences of punctuation and whitespace to single underscores. The sanitised string will start with an alphanumeric character.
"Word" --> "Word" "a Sentence" --> "a_Sentence" "trailing Withespace \t \n" --> "trailing_Withespace" "with a lot \nof Whitespace" --> "with_a_lot_of_Whitespace" "@with\".\'much ($punctuation)[]!" --> "@with.much_($punctuation)" "§&Ω%€ leading garbage" --> "leading_garbage" "mixed Ω garbage" --> "mixed_garbage" "Bääääh!!" --> "Bh"
Definition at line 65 of file util.cpp.
References util::isValid(), and util::sanitise().
Referenced by util::sanitise().