std :: call_once与std :: mutex进行线程安全的初始化 [英] std::call_once vs std::mutex for thread-safe initialization

查看:635
本文介绍了std :: call_once与std :: mutex进行线程安全的初始化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对std::call_once的目的有些困惑.需要明确的是,我确切地了解了std::call_once 的用途以及使用方法.通常用于原子地初始化某些状态,并确保只有一个线程初始化状态.我还在线上看到了许多尝试使用std::call_once创建线程安全的单例.

I'm a bit confused about the purpose of std::call_once. To be clear, I understand exactly what std::call_once does, and how to use it. It's usually used to atomically initialize some state, and make sure that only one thread initializes the state. I've also seen online many attempts to create a thread-safe singleton with std::call_once.

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

As demonstrated here, suppose you write a thread safe singleton, as such:

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

好的,我明白了.但是我认为std::call_once真正保证的唯一事情是,所传递的函数将仅 执行一次.但这是否保证如果在多个线程之间存在调用该函数的竞争,并且一个线程获胜,则其他线程将阻塞,直到获胜线程从线程返回.打电话吗?

Okay, I get the idea. But I thought that the only thing std::call_once really guarantees is that the passed function will only be executed once. But does it also guarantee that if there is a race to call the function between multiple threads, and one thread wins, the other threads will block until the winning thread returns from the call?

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

Because if so, I see no difference between call_once and a plain synchronization mutex, like:

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

    return *m_instance;
}

因此,如果std::call_once确实迫使其他线程阻塞,那么std::call_once与常规互斥锁相比有什么好处?再想一想,std::call_once肯定会必须强制其他线程阻塞,否则在用户提供的函数中完成的任何计算都不会同步.再说一次,std::call_once在普通互斥锁之上提供了什么?

So, if std::call_once indeed forces other threads to block, then what benefits does std::call_once offer over a regular mutex? Thinking about it some more, std::call_once would certainly have to force the other threads to block, or whatever computation was accomplished in the user-provided function wouldn't be synchronized. So again, what does std::call_once offer above an ordinary mutex?

推荐答案

call_once为您做的一件事是处理异常.也就是说,如果第一个线程进入的函子在函子内部引发了异常(并将其传播出去),则call_once不会认为满足call_once的要求.允许随后的调用再次进入函子,以试图无例外地完成它.

One thing that call_once does for you is handle exceptions. That is, if the first thread into it throws an exception inside of the functor (and propagates it out), call_once will not consider the call_once satisfied. A subsequent invocation is allowed to enter the functor again in an effort to complete it without an exception.

在您的示例中,特殊情况也得到了正确处理.但是,很容易想到一个更复杂的函子,其中特殊情况将无法得到正确处理.

In your example, the exceptional case is also handled properly. However it is easy to imagine a more complicated functor where the exceptional case would not be properly handled.

所有这些,我注意到call_once对于function-local-statics是多余的.例如:

All this being said, I note that call_once is redundant with function-local-statics. E.g.:

CSingleton& CSingleton::GetInstance()
{
    static std::unique_ptr<CSingleton> m_instance(new CSingleton);
    return *m_instance;
}

或更简单地说:

CSingleton& CSingleton::GetInstance()
{
    static CSingleton m_instance;
    return m_instance;
}

以上等效于您使用call_once的示例,恕我直言,更简单.哦,除了销毁的顺序与您的示例之间的细微差别之外.在这两种情况下,m_instance都以相反的顺序破坏.但是构造的顺序是不同的.在您的m_instance中,是相对于同一翻译单元中具有文件本地范围的其他对象构造的.使用本地局部函数功能,第一次执行GetInstance时会构造m_instance.

The above is equivalent to your example with call_once, and imho, simpler. Oh, except the order of destruction is very subtly different between this and your example. In both cases m_instance is destroyed in reverse order of construction. But the order of construction is different. In your m_instance is constructed relative to other objects with file-local scope in the same translation unit. Using function-local-statics, m_instance is constructed the first time GetInstance is executed.

这种差异可能对您的应用程序很重要,也可能不重要.通常,我更喜欢函数局部静态解决方案,因为它是惰性"的. IE.如果应用程序从不调用GetInstance(),则不会构造m_instance.而且,在应用程序启动期间没有时间试图同时构造许多静态变量.您只有在实际使用时才支付建造费用.

That difference may or may not be important to your application. Generally I prefer the function-local-static solution as it is "lazy". I.e. if the application never calls GetInstance() then m_instance is never constructed. And there is no period during application launch when a lot of statics are trying to be constructed at once. You pay for the construction only when actually used.

这篇关于std :: call_once与std :: mutex进行线程安全的初始化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆