Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/161.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/EmptyTag/159.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ std::call_once vs std::mutex用于线程安全初始化_C++_Multithreading_C++11 - Fatal编程技术网

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

C++ std::call_once vs std::mutex用于线程安全初始化,c++,multithreading,c++11,C++,Multithreading,C++11,我对std::call\u once的目的有点困惑。说清楚一点,我确切地理解了std::call_once的功能,以及如何使用它。它通常用于以原子方式初始化某些状态,并确保只有一个线程初始化该状态。我也在网上看到过很多尝试,试图用std::call\u once创建线程安全的单例 作为,假设您编写了一个线程安全的单例,如下所示: CSingleton& CSingleton::GetInstance() { std::call_once(m_onceFlag, [] {

我对
std::call\u once
的目的有点困惑。说清楚一点,我确切地理解了
std::call_once
的功能,以及如何使用它。它通常用于以原子方式初始化某些状态,并确保只有一个线程初始化该状态。我也在网上看到过很多尝试,试图用
std::call\u once
创建线程安全的单例

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

CSingleton& CSingleton::GetInstance()
{
    std::call_once(m_onceFlag, [] {
        m_instance.reset(new CSingleton);
    });
    return *m_instance.get();
}
好吧,我明白了。但是我认为std::call_one真正能保证的是传递的函数只执行一次。但它是否也能保证,如果在多个线程之间存在调用函数的竞争,并且一个线程获胜,那么其他线程将阻塞,直到获胜的线程从调用中返回

因为如果是这样,我看不出
call\u 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;
}
CSingleton&CSingleton::GetInstance()
{
std::唯一的_锁(m_互斥);
如果(!m_实例)
{
m_instance.reset(新CSingleton);
}
lock.unlock();
返回*m_实例;
}
那么,如果
std::call_once
确实强制其他线程阻塞,那么
std::call_once
与常规互斥锁相比有什么好处呢?再仔细想想,
std::call_once
肯定会强制其他线程阻塞,否则用户提供的函数中完成的任何计算都不会同步。那么,在普通互斥锁之上,
std::call\u once
提供了什么呢?

如果您阅读,您会发现
std::call_once
并不能保证数据竞争,它只是一个用于执行一次操作(跨线程工作)的实用函数。你不应该假设它有任何接近互斥的效果

例如:

#include <thread>
#include <mutex>

static std::once_flag flag;

void f(){
    operation_that_takes_time();
    std::call_once(flag, [](){std::cout << "f() was called\n";});
}

void g(){
    operation_that_takes_time();
    std::call_once(flag, [](){std::cout << "g() was called\n";});
}

int main(int argc, char *argv[]){
    std::thread t1(f);
    std::thread t2(g);
    t1.join();
    t2.join();
}
#包括
#包括
静态标准::once_标志;
void f(){
需要花费时间()的操作;

std::call_once(flag,[](){std::cout调用_once
为您做的一件事是处理异常。也就是说,如果进入它的第一个线程在functor内部抛出异常(并将其传播出去)<代码> Call一次不会考虑<代码> Call一次满足。后续调用允许再次进入函数,以完成它,没有例外。

在你的例子中,例外情况也得到了正确的处理。然而,很容易想象一个更复杂的函子,例外情况不会得到正确的处理

尽管如此,我注意到,
call\u once
与函数本地静态是冗余的。例如:

CSingleton& CSingleton::GetInstance()
{
    static std::unique_ptr<CSingleton> m_instance(new CSingleton);
    return *m_instance;
}
上面的例子相当于你的例子,用
call\u once
,imho,更简单。哦,除了销毁的顺序与你的例子非常微妙的不同之外。在这两种情况下,
m\u实例
都是按相反的构造顺序销毁的。但是构造的顺序是不同的。在你的
m\u实例
i相对于同一翻译单元中具有文件局部作用域的其他对象构造的。使用函数局部静态,在第一次执行
GetInstance
时构造
m_instance


这种差异可能对应用程序很重要,也可能不重要。通常我更喜欢函数本地静态解决方案,因为它是“惰性的”。例如,如果应用程序从不调用
GetInstance()<代码> >代码> MyStase,但在应用程序启动时没有一段时间,一次尝试构建很多静态结构。您只在实际使用时才为该结构付费。

< P>标准C++解决方案的细微变化是在通常的一个内使用lambda:

// header.h
namespace dbj_once {

    struct singleton final {};

    inline singleton & instance()
    {
        static singleton single_instance = []() -> singleton {
            // this is called only once
            // do some more complex initialization
            // here
            return {};
        }();
        return single_instance;
    };

 } // dbj_once
请遵守

  • 匿名名称空间暗示内部变量的默认静态链接。因此不要将其放入其中。这是头代码
  • 值得重复:这在存在多线程(MT)时是安全的,并且所有主要编译器都支持它
  • 里面有一个lambda,保证只调用一次
  • 此模式也可以安全地用于仅标头的情况

你试过/测试过吗?@Brandon,测试比赛条件是不现实的。你为什么调用
lock.unlock()
在第二个示例中?无法保证线程安全?
只执行可调用对象f一次,即使从多个线程调用也是如此。
我感到困惑。您粘贴的链接似乎与您所说的相反:
在上述所选函数的执行为c之前,组中没有调用返回成功完成,也就是说,不会通过异常退出。
我误解了什么吗?@BryanChen是的,但它不能保证不会出现数据竞争。你能举个例子吗?我也认为该链接保证了互斥体的行为。只是,更简洁地说,就像C++11 std::remove\u if一样(还有无数其他的东西)并没有给你提供你无法获得的功能。但是你的例子中的函数local static thread safe是如何实现的呢?C++11指定它是线程安全的。我知道,因为我必须为libc++abi实现它。:-)参见6.7[stmt.dcl]/警告:VS-2013还没有实现函数本地静态的线程安全构造。但是其他人都实现了。哇。C++11真的修复了C++03中关于线程安全的许多垃圾。我已经习惯于看到函数本地静态,并且认为“OMG不是线程安全的!”请注意,GCC支持一个编译器标志来禁用讨厌的线程安全本地静态,并且请注意,有些人(例如我)将其添加到默认编译器选项中。在没有传达不希望这样做的方式的情况下,使本地静态线程安全是一个非常愚蠢的设计决策。这是
// header.h
namespace dbj_once {

    struct singleton final {};

    inline singleton & instance()
    {
        static singleton single_instance = []() -> singleton {
            // this is called only once
            // do some more complex initialization
            // here
            return {};
        }();
        return single_instance;
    };

 } // dbj_once