[thread] synchronization objects of static storage duration

Hi all, 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 classes. 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 as follows: static pthread_once_t foo_once = PTHREAD_ONCE_INIT; static pthread_mutex_t foo_mutex; void foo_init() { pthread_mutex_init(&foo_mutex, NULL); } void foo() { pthread_once(&foo_once, foo_init); pthread_mutex_lock(&foo_mutex); /* Do work. */ pthread_mutex_unlock(&foo_mutex); } With static initialization, the same routine could be coded as follows: static pthread_mutex_t foo_mutex = PTHREAD_MUTEX_INITIALIZER; void foo() { pthread_mutex_lock(&foo_mutex); /* Do work. */ pthread_mutex_unlock(&foo_mutex); }" 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 -- Anthony Williams Software Developer Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk
participants (1)
-
Anthony Williams