
Hello, 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 thread lifetime. 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

Christophe Meessen <christophe@meessen.net> writes:
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 thread lifetime.
Is there some reason you can't embed a reference to a mutex? -- Dave Abrahams Boost Consulting http://www.boost-consulting.com

Hi Cristophe,
Hello,
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
that's not a problem. You can always put a non-copyable object inside a struct and pass a smart_pointer to that struct.
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.
same can happen with current [thread] library.
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.
are you sure? what if the thread is ::Terminated?
Thread objects are dynamically allocated and referenced through intrusive pointer. Thus thread objects can have
same could be achieved with smart pointers.
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.
the self() is a pretty good idea. However, the big issue is not when [thread] is used within a custom application, where you know exactly what TSS data you need. The issue is when you develop a library that depends on [thread] and needs TSS.
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
I'm not sure I follow. Could you please give an example?
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.
I don't think this has bought you anything. In fact, I think it's more prone to error, since who's stopping you to do: void crazy() { some_lock l(mutex); l.lock(); }
We believe there was a possible confusion in the initial lock state of boost locks. It is not orthogonal.
Again, could you give an example? Best, John

Christophe Meessen wrote:
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 thread lifetime.
This is not correct. It does not require a copy. Use ref(). Or bind(&X::run, shared_ptr<X>(new X)), if you prefer to stay "object oriented". It creates a copy by default because this is the safer alternative (the lifetime of the function object is managed by the library).
The thread object is the obvious place to use as thread local storage.
The general drawback of using member variables as thread local storage is that (a) you need 'this' in order to access them, (b) if a low-level routine needs TLS data in order to make itself reentrant (or to achieve better performance - thread-specific free lists come to mind as an example), it can't allocate it itself; you'll need to change the thread class and add a member. Which makes low-level library design a bit difficult. This is a common theme; many approaches work fine in a tightly controlled environment, when you write all of the code, but do not scale to distributed, modular environments.
participants (4)
-
Christophe Meessen
-
David Abrahams
-
John Torjo
-
Peter Dimov