C++中线程安全单例模式的正确实现方式

zuixin 2020-01-03

为什么说DCLP不是线程安全的

DCLP(Double Checked Locking Pattern),即双检锁模式:

class Foo {
public:
    static Foo* getInstance() noexcept {
        if (nullptr == s_pInstance) {
            std::lock_guard<std::mutex> lock(s_mutex);
            if (nullptr == s_pInstance) {
                s_pInstance = new Foo();
            }
        }

        return s_pInstance;
    }

private:
    static Foo* volatile s_pInstance;
    static std::mutex s_mutex;
};

在C++中,volatile关键字只保证数据的可见性,并不保证数据的一致性。所以当外面的判断s_pInstance非空的时候,由于可能的指令重排,这时对象可能还没有真正的构造好,使得程序无法按照预定的行为执行。

新标准中更好的解决方案

在C++中,静态局部变量的初始化发生在程序第一次执行到变量声明的地方:

static Foo* getInstance() {
    static Foo s_instance;
    return &s_instance;
}

在C++98中,并没有考虑线程安全的问题,只是简单地用一个条件判断来实现该语义:

static bool s_initialized = false;
static char s_buf[sizeof(Foo)];

static Foo* instance()
{
    if (!s_initialized) {
        s_initialized = true;
        new (s_buf) Foo();
    }

    return (reinterpret_cast<Foo*>(s_buf));
}

C++11中定义了线程安全的行为,如下是标准草案的6.7节内容:

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. If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.

  • 若初始化抛出异常将继续保持未初始化状态
  • 若正在初始化,其它运行初始化语句线程将被阻塞
  • 若正在初始化的线程递归调用初始化语句行为未定义

相关推荐