From: Anthony Williams (anthony_w.geo_at_[hidden])
Date: 2005-09-08 08:54:45
I've been thinking a lot about synchronization primitives, especially
around call_once, but also around mutexes, semaphores and conditions.
Ensuring that they are correctly initialized, safely, is a hard problem,
especially if we try and allow them to be used both as objects with static
storage duration, and with non-static storage duration, or as members of other
Firstly, objects with static storage duration must be completely initialized
statically, and not require any dynamic initialization. This is because
dynamic initialization is not thread safe in general, and exposes the object
to potential race conditions on the initialization, unless an alternative
synchronization mechanism is used to ensure thread safety. The POSIX spec
gives a good example, relating to pthread_mutex_init:
"Without static initialization, a self-initializing routine foo() might look
static pthread_once_t foo_once = PTHREAD_ONCE_INIT;
static pthread_mutex_t foo_mutex;
/* Do work. */
With static initialization, the same routine could be coded as follows:
static pthread_mutex_t foo_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Do work. */
This requires that the objects are PODs or aggregates of PODs, so that no
constructors are run (which would be dynamic initialization); if
zero-initialization is not satisfactory, then a static initializer akin to
PTHREAD_MUTEX_INITIALIZER can then be used.
Secondly, objects of non-static storage duration cannot assume anything about
the underlying memory, so they must be initialized dynamically. If the same
class type is to be used for both purposes, this cannot be with a constructor,
so it must be with an initializer. Whether this is the same as the static
initializer is another question.
Thirdly, these requirements apply recursively to any class which contains a
synchronization primitive as a member: if a class has a constructor, then it
is not safe for static initialization. If it does not have a constructor, then
it requires an initializer to ensure safe use as an object with non-static
storage duration. For such a class with a constructor, the synchronization
primitive member must therefore be initialized in the member-initialization
list. This implies that there must be a suitable initializer provided, as an
aggregate initializer which might suffice for standalone variables cannot be
used for member initialization.
This implies to me that we probably therefore want two versions of each
synchronization primitive. The first is a POD, which is therefore safe for
static initialization. The second is non-POD, and has an appropriate default
constructor, which is therefore safe for non-static objects. Any object which
contains a synchronization primitive as a member must therefore choose which
version to use, and therefore limit its own use as a consequence.
Section 3.6.2 of the C++ standard allows implementers leeway to statically
initialize objects with the result of dynamic initialization if such
initialization yields a constant value, and doesn't affect any other
object. On specific compilers and platforms where we can be sure that the
implementors have taken advantage of this leeway, we can possibly relax some
of these guidelines.
Also, some compilers and platforms guarantee that dynamic initialization of
objects with static storage duration is thread-safe. On such platforms, it is
therefore safe for synchronization primitives to be non-POD objects with
appropriate constructors, under all circumstances.
-- 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