x::run
()
adapts std::thread
and std::packaged_task
to LibCXX's
reference-counted object model.
x::run
() takes a reference to an object with a
run
() method, which gets invoked in a new
thread.
#include <x/obj.H> #include <x/ref.H> #include <x/threads/run.H> class buttonObj : virtual public x::obj { public: bool run(int x, int y); buttonObj() noexcept; ~buttonObj(); // ... }; typedef x::ref<buttonObj> button; // ... button okButton=button::create(); // ... x::runthread<bool> ret=x::run(okButton, 100, 100); // ... if (ret->terminated()) // ... ret->wait(); bool status=ret->get();
The first parameter to
x::run
()
is a reference to an object that itself implements a
run
() method, possibly with parameters.
x::run
takes the reference to an object,
and invokes the referenced object's run
(),
forwarding to it the remaining arguments to
x::run
().
The referenced object's run
() gets invoked in a
new execution thread, meanwhile x::run
()
returns a
x::runthread<
.
The arguments to T
>run
() must be
copy-constructible.
x::run
() copies the arguments, and
the thread object's run
() gets invoked with the
copied arguments, which get destroyed after
run
() returns, and just before the new
execution thread terminates.
x::run
() takes care of instantiating
a new std::thread
, and joining the
thread when it's done. With x::run
(), there is
no concept of joinable and detached threads. The thread started by
x::run
() holds its own reference on the object
whose run
() method got invoked, and releases
the reference when run
() returns.
The calling thread may keep its reference to the object, for other
purposes, or all of its references to the object can go out of scope,
but the new thread's reference keeps the object from getting destroyed
until run
() returns.
When run
() returns, an internal cleanup thread
takes care of joining the terminated thread.
x::run
() returns a
x::runthread<
,
where T
>T
is the type of the return value
from the thread object's run
().
Its terminated
() method
returns a boolean that indicates
whether run
() in execution thread started by
x::run
()
returned and the thread has terminated.
get
() waits for the
run
() method to return, and if
T
is not void
,
returns its return value (the return type of
run
()
must be, naturally, copy-constructible.
If the execution thread terminated with an exception, rather than
returning from its run
(),
get
() rethrows that exception.
wait
(),
wait_for
(), and
wait_until
() wait until the execution
thread terminates (either by returning from its
run
(), or by throwing an uncaught exception).
wait
() waits indefinitely, until
run
() returns.
wait_for
() and
wait_until
() take
a std::chrono::duration
or a
std::chrono::time_point
, respectively, setting
the timeout for the wait.
All execution threads are expected to terminate before the
main application exits by returning from main
()
or by calling exit()
.
A warning message gets printed to standard error if not, and
abort
() follows if the remaining
threads still do not terminate after thirty seconds.
x::runthread<T>
refers to a
template class that inherits from a non-template
x::runthreadbaseObj
class, whose reference type is
x::runthreadbase
.
It implements just the terminated
() and
wait
() methods. In contexts where the return
value from an object's run
() is not required,
this allows implementation
of non-templated code that waits for arbitrary execution
threads to terminate.
pthread_cancel
(3) cannot be used in code that uses
LibCXX. pthread_cancel
(3) terminates a thread without
unwinding the stack. The stack may contain references to
reference-counted objects, or other objects
whose destructors contain critical functionality.
Using pthread_cancel
(3) will result in memory leaks,
deadlocks, and other unstable behavior. This is likely to be the case with
any C++-based process of non-trivial complexity,
not just LibCXX. The only safe way to forcibly terminate the thread is
by throwing an exception that unwinds the entire stack frame.
x::msgdispatcherObj
provides a convenient message-based thread design pattern which
supplies a stop
() method that sends a message to the
running thread that causes it to throw an exception and terminate, in an
orderly manner.
run
() methods must be copy-constructible
It's already been mentioned that arguments to execution threads'
run
() methods must have a copy constructor.
#include <x/obj.H> #include <x/ref.H> #include <x/threads/run.H> class widget; class rendererObj : virtual public x::obj { public: void run(widget &w); // ... }; void runawidget(const x::ref<renderObj> &r, const widget &w) { x::run(r, w); }
In this example, the thread object's run
()
method takes a reference to a non-constant widget object. However,
the argument to x::run
() is
a reference to a constant widget object.
This is because the arguments get copied for the new execution thread.
The thread object's run
() method get invoked
with the thread's copy of each argument passed to
x::run
().
The run
() method's prototype can specify
either a constant reference, or a mutable reference to a type, in either
case it'll get resolved.
This means that although an object may have overloaded
run
() methods, a run
()
that takes a reference to a constant object will never be used if
there's an overloaded run
() method that takes
a reference to a mutable object (as long as all other parameters are
the same).
template<typename objClass, typename baseClass, typename ...Args> auto start(const x::ref<objClass, baseClass> &thread, Args && ...args) -> typename x::runthread<decltype(thread->run(x::invoke_run_param_helper <Args>()...))> // ...
As described previously, x::run
() returns a
x::runthread<
,
where T
>T
gives the return type of
the object's run
(). Sometimes its useful
to define a template that uses a related type, such as a corresponding
x::ptr
, instead.
x::invoke_param_helper
() helps
in construing the parameters to a run
as if
they were invoked in the executed thread, given the original
parameters to x::run
().
Its definition is very short:
template<typename param_type> class run_param { public: typedef typename std::decay<param_type>::type &type; }; template<typename param_type> typename run_param<param_type>::type &invoke_run_param_helper();
Each parameter to x::run
() is decay-copied into
the new execution thread's context, then passed by reference to a
run
() method.
To supply a default thread name for logging
purposes, subclass from
x::runthreadname
and implement getName
(), as follows:
#include <x/obj.H> #include <x/runthreadname.H> class myThreadObj : virtual public x::obj, public x::runthreadname { public: // ... void run( ... ); std::string getName() const { return "mythread"; } };
x::run
() invokes
getName
() prior to invoking the thread object's
run
(), to set the thread's name, for logging
purposes.
By default,
x::run
() has no issues with starting multiple
concurrent threads, at the same time, using the same class instance.
To prevent that, subclass
x::runthreadsingleton
:
#include <x/obj.H> #include <x/runthreadsingleton.H> class myThreadObj : virtual public x::obj, virtual public x::runthreadsingleton { // ... };
x::run
() now throws an exception if an earlier
x::run
() spawned a thread on the same object,
and the earlier thread is still running.
Use virtual public inheritance to subclass
x::runthreadsingleton
, so that a subclass
that inherits from multiple thread singletons gets a single instance
of x::runthreadsingleton
.
auto ret=x::run_lambda([] (const x::netaddr &addr) { x::fd sock=addr->connect(); return sock; }, x::netaddr::create("localhost", "http")); ret->wait(); x::fd val=ret->get();
x::run_lambda
()
starts a new thread that starts running the lambda given as its
first parameter, then returns a
x::runthread<
,
that can be used to wait for the lambda to finish, then retrieve
the return value from the lambda.
T
>
x::run
() takes care of the hard work of
using std::thread
directly, notably the
requirement to join the started thread before
std::thread
's destructor gets invoked.
std::thread
is privately owned by a
x::runthread<
that gets returned by T
>x::run
().
Additionally, the started thread maintains its own reference on the
thread object, and the x::runthread
instance.
If all other references to the thread object go out of scope, the
thread object does not get destroyed before its
run
() returns, since it holds the
last remaining reference to the thread object. Furthermore, there's
also an indirect reference on the
x::runthread
, with its private
std::thread
instance, that does not get
released until after run
(), at which point
its turned over to a cleanup thread.
The first call to x::run
() starts a separate
thread that joins all threads that return from thread objects'
run
()s. A single cleanup thread gets used
for all x::run
()s-started execution threads.
A terminated thread's x::runthread
,
containing its std::thread
, gets turned over
to the cleanup thread, which joins it, sets
its x::runthread
's
terminated
() to true, and makes the
return value from the thread object's run
()
get
()able from the
x::runthread
.
Even if the x::runthread
that's returned by
x::run
() gets ignored, an internal reference
to it gets held by the executing thread. This keeps it from getting
destroyed until run
() returns and the thread
terminates.
When that happens, the x::runthread
gets
turned over to the cleanup thread, which joins the terminated
thread, and then releases the last reference on the
x::runthread
, finally destroying it.
For this reason, the x::runthread
could,
but should not be used as a mcguffin. The cleanup thread is unable
to continue joining other threads until any destructor callbacks
return.
fork
() invalidates all threads and all thread
support library classes. They are used extensively by LibCXX, so after
a fork
(), the child processes cannot use any
LibCXX classes or methods.
fork
() has no effect in the parent process.
All threads remain valid. All thread objects remain valid.
Use x::forkexec
to safely fork
() and exec
()
another process.
See also: Chapter 15, Forking other processes.
The full log message format generated by the
application logging subsystem
includes the name of the running thread. This is set by
getName
() method of the thread object passed
to x::run
(),
if the thread class inherits from
x::runthreadname
,
or a default name.