Страницы

Поиск по вопросам

понедельник, 15 октября 2018 г.

Потокобезопасное создание объекта локального статического мьютекса?

Предположим, требуется организовать защиту произвольного кода локальным статическим мьютексом:
void MyClass::myMethod() { static QMutex mutex;
mutex.lock(); // защищённый код ... mutex.unlock(); }
Насколько мне известно, если вместо QMutex будет иной локальный статический тип (например, QString), то запросто может случиться "гонка" и, скажем, два конкурирующих потока создадут две копии объекта.
Несмотря на то, что в справке Qt для QMutex указано, что все методы являются потокобезопасными, нет уверенности, что заявленное справедливо и для его конструктора.
Будет ли в контексте MyClass::myMethod() создание объекта мьютекса при первом обращении потокобезопасным?


Ответ

Начиная с C++11 инициализация статических переменных потокобезопасна (§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

Для более ранних стандартов это не гарантировалось, хотя некоторые компиляторы обеспечивали такую защиту (например, GCC).
Рассмотрим конструктор QMutex
QMutex::QMutex(RecursionMode mode) { d_ptr.store(mode == Recursive ? new QRecursiveMutexPrivate : 0); }
Qt широко использует идиому Pimpl, d_ptr как раз и является указателем на реализацию и имеет тип QBasicAtomicPointer. При многопоточном доступе к конструктору могут возникнуть случаи, когда объект QRecursiveMutexPrivate будет сконструирован несколько раз, что приведет к утечкам памяти. Если мьютекс нерекурсивный, то конструктор потокобезопасен.
Другая, более серьезная, проблема связана с race condition: например, два потока A и B начинают инициализацию переменной mutex, поток A завершает инициализацию и вызывает функцию lock(), управление передается потоку B, который в свою очередь завершает инициализацию, что приводит в итоге к неопределенному поведению.

Комментариев нет:

Отправить комментарий