Может ли мьютекс, определенный статически в теле функции, быть в состоянии заблокировать правильно? - PullRequest
0 голосов
/ 10 ноября 2018

Может ли мьютекс, определенный статически, в теле функции, правильно блокироваться? В настоящее время я использую этот шаблон в своей системе ведения журнала, но я еще не проверял его безопасность потоков.

void foo () {
    static std::mutex mu;
    std::lock_guard<std::mutex> guard(mu);
    ...
}

Ответы [ 2 ]

0 голосов
/ 10 ноября 2018

Ответ NathanOliver не точен: mu на самом деле статически инициализирован - это означает, что перед любой динамической инициализацией и, следовательно, также перед тем, как любой пользовательский код может вызвать mu.lock() (напрямую или с помощью * 1005) *).

Тем не менее, ваш вариант использования безопасен - на самом деле, std::mutex инициализация даже более безопасна, чем предполагалось в предыдущем ответе.

Причина этого заключается в том, что любая переменная со статической продолжительностью хранения (✓ check), которая инициализируется константным выражением (где вызов конструктора constexpr явно рассматривается как таковой - ✓ check), равна инициализированная константа , которая является подмножеством статическая инициализация . Вся статическая инициализация происходит строго перед любой динамической инициализацией и, следовательно, перед тем, как ваша функция может быть вызвана в первый раз. ( basic.start.static / 2 )

Это относится к std::mutex, потому что std::mutex имеет только один жизнеспособный конструктор, конструктор по умолчанию, и он указан как constexpr. ( thread.mutex.class )

Следовательно, в дополнение к обычной гарантии атомарности, что C ++ 11 и выше обеспечивает динамическую инициализацию статических переменных в области действия функции, другие std::mutex экземпляры со статическим хранилищем также полностью не зависят от инициализации вопросы заказа, например:

 #include <mutex>

 extern std::mutex mtx;
 unsigned counter = 0u;
 const auto count = []{ std::lock_guard<std::mutex> lock{mtx}; return ++counter; };
 const auto x = count(), y = count();
 std::mutex mtx;

Если бы mtx был динамически инициализирован, этот код проявил бы неопределенное поведение, потому что тогда инициализатор mtx работал бы после того из динамически инициализированных x и y, и поэтому mtx использоваться до его инициализации.

(В pthread или общих реализациях <thread>, использующих pthread, этот эффект достигается использованием константного выражения PTHREAD_MUTEX_INITIALIZER.)

PS: Это также верно для экземпляров std::atomic<T>, если аргумент, передаваемый конструктору, является константным выражением. Это означает, например, что вы можете легко сделать спин-блокировку на основе std::atomic<IntT>, которая невосприимчива к проблемам порядка инициализации. std::once_flag имеет такое же желаемое свойство. std::atomic_flag со статической продолжительностью хранения также может быть статически инициализирован двумя способами:

  • std::atomic_flag f;, инициализируется нулями (из-за статической длительности хранения и из-за тривиального значения по умолчанию). Обратите внимание, что состояние флага, тем не менее, не определено, что делает этот подход довольно бесполезным.

  • std::atomic_flag f = ATOMIC_FLAG_INIT; постоянно инициализируется и не устанавливается. Это то, что вы на самом деле хотите использовать.

0 голосов
/ 10 ноября 2018

Да, это нормально. При первом вызове функции mu она будет инициализирована (и это гарантированно будет поточно-ориентированным и будет происходить только один раз), а затем guard заблокирует ее. Если другой поток вызывает foo, он будет ждать на

std::lock_guard<std::mutex> guard(mu);

до тех пор, пока не будет завершен первый вызов foo и guard не будет уничтожен, разблокируя mu.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...