Boost logo

Boost :

From: Anthony Williams (anthony_w.geo_at_[hidden])
Date: 2006-10-08 10:06:03


Roland Schwarz <roland.schwarz_at_[hidden]> writes:

> Anthony Williams wrote:
>> Here's a win32-only static once_init implementation. This could be used as a
>> general purpose static-init template, or as a basis for making boost::mutex
>> work both as a static or non-static object, with a default constructor.
>>
>> Comments please,
>
> Anthony, I did not yet have had the time to walk through all details
> of your proposal, but the main problem why this isn't thread safe is:
>
> void f()
> {
> static once_init<A> a;
> a->f();
>
> }
>
> class once_init does have a user declared constructor and a destructor!
> And this definitely is _not_thread safe.

Yes, agreed. That's why the operator-> also does a lazy_init().

This is completely platform- and implementation-dependent code, precisely the
sort of thing that needs to be wrapped in a library, and not written by the
general public.

On MSVC 7.1, the compiler will call the constructor in the first thread that
starts the function, and not any others. It will also not wait for the
constructor to finish.

For objects of static storage duration, the memory is initialized to 0 upon
startup.

Since the constructor doesn't read the data, and only sets it with the final
InterlockedExchange, the constructor doesn't care what the initial data value
is.

However, the lazy_init does care --- it uses an interlocked read to ensure
that it correctly reads the value in memory. If it is non-zero, then the
constructor must have run already, else we're racing against the constructor,
which is running in another thread. If the constructor has run, we're fine. If
we're racing, we run the same init routine as the constructor, which is
designed to only run once.

The init routine tries to create a manual-reset event object, initially in the
non-signalled state. This is a named object, so the same one is returned from
every call, but GetLastError() returns ERROR_ALREADY_EXISTS if it already
exists. If it already exists, we wait for it, else we're the first thread to
run the init routine (we won the race), so we actually do the initialization,
store the event handle for use by the destructor, and as the flag that
construction is done, and signal the event, so the other racing threads can
resume, safe in the knowledge that everything is fine.

For the destructor, the thread that actually runs the constructor will
schedule a destructor call with atexit (on MSVC), so the destructor runs once,
too.

I should have explained the logic when I posted it, but I was short of time.

Anthony

-- 
Anthony Williams
Software Developer
Just Software Solutions Ltd
http://www.justsoftwaresolutions.co.uk

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