Boost logo

Boost :

From: Tobias Schwinger (tschwinger_at_[hidden])
Date: 2007-08-23 20:19:43


Andrey Semashev wrote:
> Hello Robert,
>
> Wednesday, August 22, 2007, 12:43:03 AM, you wrote:
>
>> Tobias Schwinger wrote:
>
>>> AFAICT, our "lightweight toolbox" is still insufficient to implement a
>>> thread-safe Singleton - I might be missing something, though. How
>>> would
>>> you initialize 'lightweight_mutex' when you can't know that ctors are
>>> run in static context (as within a shared library)?
>
>>> Maybe it's possible to make 'detail::atomic_count' an aggregate and
>>> provide a macro for initialization (just as pthread does for its
>>> synchronization primitives). Then it would be trivial to implement a
>>> 'lightweight_once' on top of it...
>
>> Please start thinking about this. I don't see why this can't be a header
>> only library. Of course in my case, the expanded headers
>> (instantiated code) will be compiled into the serializatoin library
>> as an implementation detail. I would much prefer that to having to
>> link in another library.
>
> Just came across this thread. I had a need of lightweight_call_once in
> my Boost.FSM library and implemented it. It is not implemented as an
> internal part of the library, but rather as a common tool, like
> lightweight_mutex.

Something you'd like to brush up as a Boost X-File ;-)?

See

     http://article.gmane.org/gmane.comp.lib.boost.devel/162951

> It can be found here:
>
> http://tinyurl.com/yjozfn

Thanks! It's great you actually wrote a reusable tool. I finally found
some time to review your code. Here are my (hopefully not too
discouraging) comments:

The pthreads implementation seems to be using a global Mutex, which is
inefficient, because it causes concurrent initializations (that might
have nothing to do with each other) to be queued.
To make things worse, that Mutex is initialized with 'pthread_once'.
Also, some platforms will not call 'mutex_destroyer' within a dynamic
library (you probably know)...
The "trigger" could contain the mutex and the macro for initialization
would contain PTHREAD_MUTEX_INITIALIZER, so its creation can be done at
compile time by setting up the appropriate bytes in the data segment
(interestingly, you use a similar technique for the "no atomics variant").

Other implementations use "while (check) sleep; stuff", which seems
sorta awkward to me. Can't we use "proper" synchronization?
Win32 provides the 'InitOnceExecuteOnce' which seems to do pretty much
all we need (it even takes a parameter to get a the state in and
boost::function and a downcast from 'PVOID' will do for the type
erasure). After putting another guard around it (to avoid the dynamic
call and the construction of the boost::function) we're all done.
   I don't know too much about other threading platforms, but I'm sure
there are similar means. We could also use a counter for the guard to
use a Semaphore (if it's more handy to do so for some platform) to
notify threads waiting for initialization to complete:

     if (is_init(trigger)) return;

     if (atomic_inc(cnt) > 1) // <-- gate point
     {
       // go to sleep, unless we missed initialization has finished
       if (! is_init(trigger)) sem.down();
       atomic_dec(cnt);
     }
     else
     {
       client_func();
       // make sure no further threads enter the gate (threads that
       // miss the change and enter anyway will leave again)
       make_called(trigger);
       // continue all threads after the gate
       for (int n; !! (n = cnt - 1) ;)
       {
         sem.up(n);
         yield(); // <-- might or might not be needed
       }
       // postcondition: sem >= 0
     }

For some platforms (such as x86) memory access is atomic, so atomic
operations are just a waste of time for simple read/write operations as
the 'is_init' and 'set_called' stuff.

There's some code that throws exceptions with pretty, formatted error
messages: So we're out of resources and execute a whole bunch of code to
format an error message... That code might run into the same problem
we're trying to report, so probably throwing something lightweight (such
as an enum) is a more appropriate choice (and also gets us rid of some
header dependencies).

Regards,
Tobias


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk