19 July 2023

C++ 核心指南目录

“Use a conventional pattern if you really need double-checked locking”

理由

双次检查锁的实现很容易出错。如果你需要自己编写双次检查锁的代码,考虑规则 CP.110 不要自己写双次检查锁的初始化代码CP.100 除非你确定需要,尽量不要用无锁编程。如果你的确需要,考虑常用的设计模式。

如果非线程安全的行为很难或很少出现,并且有更快的线程安全测试进行保障的时候,使用双次检查锁模式并不违反规则CP.110 不要自己写双次检查锁的初始化代码

坏例子

使用 volatile 并不能确保先进性检查后执行的代码能够线程安全。请查看 CP.200: 只使用 volatile 访问非 C++ 内存

mutex action_mutex;
volatile bool action_needed;

if (action_needed) {
    std::lock_guard<std::mutex> lock(action_mutex);
    if (action_needed) {
        take_action();
        action_needed = false;
    }
}

好例子

mutex action_mutex;
atomic<bool> action_needed;

if (action_needed) {
    std::lock_guard<std::mutex> lock(action_mutex);
    if (action_needed) {
        take_action();
        action_needed = false;
    }
}

如果获取加载的方式比顺序一致加载效率更好的话,优化的内存顺序可能有些好处。

mutex action_mutex;
atomic<bool> action_needed;

if (action_needed.load(memory_order_acquire)) {
    lock_guard<std::mutex> lock(action_mutex);
    if (action_needed.load(memory_order_relaxed)) {
        take_action();
        action_needed.store(false, memory_order_release);
    }
}