From: Christophe Meessen (christophe_at_[hidden])
Date: 2004-04-19 08:20:31
regarding thread local storage we have come up with a different
perspective of the problem.
First we where not very happy with boost threads because it requires a
copy of the functor object. This does not allow to put non copyable
object as member variables of the functor object. Knowing that mutex are
none copyable objects, for good reasons, one understand that this API is
far too constraining. I guess such design choices was driven by the
requirement that the functor object lifetime is tighlty linked to the
The join function is also too limited synchronization mechanism. And
finally we where also not happy with the thread local storage approach.
We seeked a design which was simple and could provide a satisfying
solution for all these aspects and we came up with the following design.
We chose the start&run model used in many thread API because it allows
to initialize the thread object before it is started and collect result
after the thread terminated.
The thread object is the obvious place to use as thread local storage.
This is because stored data is explicitely defined and its use is
statically checked. It is also automatically destroyed with the thread
object is destroyed.
Thread objects are dynamically allocated and referenced through
intrusive pointer. Thus thread objects can have none copyable member
variables and getting out of scope doesn't destroy the thread object.
The thread object life time is controlled through an intrusive smart
pointer. When the internal thread main function start it instantiate a
smart pointer on itself as local variable. Then it calls the virtual run
by changing the 'this' context to the objects context. In the run
method users have access to the thread object member variables and methods.
When the thread main function terminate normally or because of an
exception, the smart pointer is destroyed. If there is no other
reference to the thread object, it is then deleted and all its local
storage information with it.
In the thread local storage managed by the system/pthread we only store
a pointer to the thread object. This is to allow to get access to the
thread object from anywhere in the code. This is equivalent of the this
variable for objects but for threads. Thus the thread class has a static
self() method returning a reference on the current thread object.
For dynamically storage, one can add a map to the thread object. The
user can pick the key and value types which are the most appropriate for
his needs. The map will be automatically destroyed when the thread
object is destroyed.
A default thread object will be provided to support classical C function
or functor calls. But the basic underlying model is to use a thread object.
It also to be noted that boost thread implementation would start the
thread before the boost thread object instantiation would complete.
Such constrains should not be imposed to the users. There is thus a need
to know when a thread has started and when it has terminated. When the
start call completes, it doesn't mean the thread has already started.
This is reflected by a state variable that would also specify if the
thread terminated normally or was aborted because of an exception or
cancelled (which should also use the exception model).
Regarding synhcronization we have also made a slight move away from
boost API which was aslo the best seen so far. We make a distinction
between locks and latch.
A lock has no method. When it is constructed it immediately attempts to
lock the mutex, and the destructor release the mutex. Thus when a lock
is instantiated, the mutex can be assumed to be locked by the thread who
performed the instantiation on its call stack.
When a latch is instantiated, the mutex is not locked. The following
methods lock, unlock, try_lock and timed_lock are used to control the
referenced mutex. When a latch variable is destroyed it automatically
unlock the mutex if it was currently locking it.
We believe there was a possible confusion in the initial lock state of
boost locks. It is not orthogonal. We also beleived there was a
performance penalty by not providing atomic lock object. The lock and
latch are encapsulated classes of mutexes. And we would by default
support two kinds of mutexes, classical pthread mutex and spin lock
mutexes. Spin lock mutexes would support locks and not latch locks. spin
locks would be used to serialize access to shared resources like smart
pointer counters, monitoring variables etc. We also droped the "scoped_"
in front of the lock class name because it is implicit, standard and
shortens the name.
-- Bien cordialement, Ch. Meessen
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk