Question:
Suppose you want to organize the protection of arbitrary code with a local static mutex:
void MyClass::myMethod() {
static QMutex mutex;
mutex.lock();
// защищённый код ...
mutex.unlock();
}
As far as I know, if instead QMutex
there is another local static type (for example, QString
), then a "race" can easily happen and, say, two competing threads will create two copies of the object.
Although the Qt help for QMutex
states that all methods are thread-safe, it is not certain that the same is true of its constructor.
In the context of MyClass::myMethod()
will the creation of the mutex object on the first call be thread-safe?
Answer:
Since C++11, static variable initialization is thread-safe ( §6.7 ):
otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization .
For earlier standards, this was not guaranteed, although some compilers provided such protection (for example, GCC).
Consider the QMutex
constructor:
QMutex::QMutex(RecursionMode mode)
{
d_ptr.store(mode == Recursive ? new QRecursiveMutexPrivate : 0);
}
Qt makes extensive use of the Pimpl idiom, d_ptr
is just an implementation pointer and is of type QBasicAtomicPointer<QMutexData>
. When accessing a constructor multithreaded, there may be cases where the QRecursiveMutexPrivate
object will be constructed multiple times, resulting in memory leaks. If the mutex is non-recursive, then the constructor is thread-safe.
Another, more serious, problem is related to the race condition
: for example, two threads A
and B
start initializing a mutex
variable, thread A
completes the initialization and calls the lock()
function, control is transferred to thread B
, which in turn completes the initialization, which results in to undefined behavior.