函数体中静态定义的互斥量是否能够正确锁定?

Is a mutex defined statically in a function body able to lock properly?

在函数体中静态定义的互斥量是否能够正确锁定?我目前在我的记录器系统中使用这个模式,但我还没有测试它的线程安全性。

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

是的,这很好。第一次调用函数时 mu 将被初始化(这保证是线程安全的并且只发生一次)然后 guard 将锁定它。如果另一个线程调用 foo 它将等待

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

直到第一次调用 foo 完成并且 guard 被销毁并解锁 mu

NathanOliver 的回答不准确:mu 实际上是 静态 初始化 – 这意味着在任何动态初始化之前,因此也在任何用户代码可以调用 mu.lock()(直接或使用 std::lock_guard<std::mutex>)。

尽管如此,您的用例是安全的 – 事实上,std::mutex 初始化甚至比之前答案建议的更安全。

这样做的原因是任何具有静态存储持续时间(✓检查)的变量都是用常量表达式初始化的(其中对 constexpr 构造函数的调用被显式地视为如此 – ✓检查),是 constant initialized,它是 static initialized 的子集。所有静态初始化严格发生在所有动态初始化之前,因此在您的函数可以第一次被调用之前发生。 (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 的初始化程序将 运行 在动态初始化的 xy,因此 mtx 将在初始化之前使用。

(在 pthread 或使用 pthread 的 <thread> 的常见实现中,此效果是通过使用常量表达式 PTHREAD_MUTEX_INITIALIZER 实现的。)

PS:对于std::atomic<T>的实例也是如此,只要传递给构造函数的参数是常量表达式即可。这意味着例如您可以轻松地基于 std::atomic<IntT> 创建一个不受初始化顺序问题影响的自旋锁。 std::once_flag 具有相同的理想 属性。具有静态存储持续时间的 std::atomic_flag 也可以通过以下两种方式之一进行静态初始化:

  • std::atomic_flag f; 是零初始化的(因为静态存储持续时间和因为它有一个微不足道的默认 c'tor)。请注意,标志的状态仍然未指定,这使得这种方法相当无用。

  • std::atomic_flag f = ATOMIC_FLAG_INIT; 是常量初始化和取消设置。这就是您实际想要使用的。