Soi*_*imn 7 c++ multithreading mutex static-variables thread-safety
是否在函数体中静态定义了互斥锁,能够正确锁定?我目前在我的记录器系统中使用这种模式,但我还没有测试它的线程安全性.
void foo () {
static std::mutex mu;
std::lock_guard<std::mutex> guard(mu);
...
}
Run Code Online (Sandbox Code Playgroud)
Nat*_*ica 10
是的,这很好.第一次调用该函数mu将被初始化(这保证是线程安全的,只发生一次),然后guard将其锁定.如果另一个线程调用foo,它将等待
std::lock_guard<std::mutex> guard(mu);
Run Code Online (Sandbox Code Playgroud)
直到第一次foo完成调用guard并被销毁解锁mu.
NathanOliver 的答案不准确:mu实际上是静态初始化的 \xe2\x80\x93\xc2\xa0 这意味着在任何动态初始化之前,因此也在任何用户代码可以调用之前mu.lock()(无论是直接调用还是使用std::lock_guard<std::mutex>)。
尽管如此,您的用例是安全的 \xe2\x80\x93\xc2\xa0 事实上,std::mutex初始化甚至比前面的答案建议的更安全。
原因是任何具有静态存储持续时间(\xe2\x9c\x93 检查)的变量都使用常量表达式初始化(其中对构造函数的调用被constexpr显式视为 \xe2\x80\x93 \xe2\x9c \x93 check) 是常量初始化,它是静态初始化的子集。所有静态初始化严格发生在所有动态初始化之前,因此在第一次调用函数之前发生。( basic.start.static/2 )
这适用于std::mutex因为std::mutex只有一个可行的构造函数,即默认构造函数,并且它被指定为constexpr. (线程.互斥体.类)
因此,除了 C++11 及更高版本在函数范围内动态初始化静态变量的通常原子性保证之外,其他std::mutex具有静态存储的其他实例也完全不受初始化顺序问题的影响,例如:
#include <mutex>\n\n extern std::mutex mtx;\n unsigned counter = 0u;\n const auto count = []{ std::lock_guard<std::mutex> lock{mtx}; return ++counter; };\n const auto x = count(), y = count();\n std::mutex mtx;\nRun Code Online (Sandbox Code Playgroud)\n\n如果mtx是动态初始化的,则此代码将表现出未定义的行为,因为mtx\ 的初始化程序将在动态初始化的xand的初始化程序之后运行y,并且mtx因此将在初始化之前使用。
(在 pthread 或<thread>使用 pthread 的常见实现中,此效果是通过使用常量表达式来实现的PTHREAD_MUTEX_INITIALIZER。)
PS:对于 的实例也是如此std::atomic<T>,只要传递给构造函数的参数是常量表达式。这意味着您可以轻松地创建一个std::atomic<IntT>不受初始化顺序问题影响的自旋锁。std::once_flag具有相同的理想特性。Astd::atomic_flag也可以通过以下两种方式之一进行静态初始化:
std::atomic_flag f;,是零初始化的(因为静态存储持续时间并且因为它有一个简单的默认c\'tor)。请注意,标志的状态仍然未指定,这使得这种方法毫无用处。
std::atomic_flag f = ATOMIC_FLAG_INIT;是常量初始化和未设置。这就是您实际想要使用的。