std :: call_once vs std :: mutex用于线程安全初始化

Sil*_*ler 15 c++ multithreading c++11

我对目的有点困惑std::call_once.需要明确的是,我明白了什么std::call_once ,以及如何使用它.它通常用于原子初始化某个状态,并确保只有一个线程初始化状态.我也在网上看过很多尝试用它来创建一个线程安全的单例std::call_once.

如此处所示,假设您编写了一个线程安全的单例,如下所示:

CSingleton& CSingleton::GetInstance()
{
    std::call_once(m_onceFlag, [] {
        m_instance.reset(new CSingleton);
    });
    return *m_instance.get();
}
Run Code Online (Sandbox Code Playgroud)

好的,我明白了.但我认为唯一std::call_once真正保证的是传递的函数只会被执行一次.但它是否保证如果在多个线程之间有一个竞争调用该函数,并且一个线程获胜,其他线程将阻塞,直到获胜线程从该调用返回?

因为如果是这样,我认为call_once普通同步互斥锁没有区别,例如:

CSingleton& CSingleton::GetInstance()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    if (!m_instance)
    {
      m_instance.reset(new CSingleton);
    }
    lock.unlock();

    return *m_instance;
}
Run Code Online (Sandbox Code Playgroud)

那么,如果std::call_once确实迫使其他线程阻塞,那么std::call_once普通互斥锁会带来哪些好处呢?再考虑一下,std::call_once肯定强制阻止其他线程,或者在用户提供的函数中完成的任何计算都不会同步.那么,std::call_once在普通互斥体之上提供什么呢?

How*_*ant 17

call_once对你有用的一件事是处理异常.也就是说,如果第一个线程进入它会在仿函数内部引发异常(并将其传播出去),call_once则不会考虑call_once满意.允许后续调用再次进入仿函数,以便在没有异常的情况下完成它.

在您的示例中,例外情况也得到了妥善处理.然而,很容易想象一个更复杂的仿函数,其中特殊情况将无法正确处理.

所有这些都说,我注意到call_once函数本地静态是多余的.例如:

CSingleton& CSingleton::GetInstance()
{
    static std::unique_ptr<CSingleton> m_instance(new CSingleton);
    return *m_instance;
}
Run Code Online (Sandbox Code Playgroud)

或者更简单:

CSingleton& CSingleton::GetInstance()
{
    static CSingleton m_instance;
    return m_instance;
}
Run Code Online (Sandbox Code Playgroud)

以上等同于你的例子call_once,而且imho,更简单.哦,除了破坏的顺序在这和你的例子之间有微妙的差别.在这两种情况下m_instance都以相反的顺序销毁.但是建筑的顺序是不同的.在您m_instance的构造中相对于具有文件局部范围的其他对象在同一个翻译单元中构建.使用function-local-statics,m_instance在第一次GetInstance执行时构造.

这种差异可能对您的申请很重要,也可能不重要.通常我更喜欢函数本地静态解决方案,因为它是"懒惰的".即如果应用程序从不调用,GetInstance()m_instance永远不会构建.在应用程序启动期间没有任何时期可以立即构建大量静力学.您只有在实际使用时才支付建筑费用.

  • C++ 11指定它是线程安全的.我知道因为我必须为libc ++ abi实现它.:-)参见规范的C++ 11的6.7 [stmt.dcl]/p4.警告:VS-2013尚未实现函数局部静态的线程安全构造.但其他人都这样做. (11认同)