我对 std::call_once
的用途有点困惑。需要明确的是,我完全了解 std::call_once
的作用 以及如何使用它。它通常用于原子地初始化某个状态,并确保只有一个线程初始化该状态。我还在网上看到许多尝试使用 std::call_once
创建线程安全的单例。
作为 demonstrated here ,假设您编写了一个线程安全的单例,如下所示:
CSingleton& CSingleton::GetInstance()
{
std::call_once(m_onceFlag, [] {
m_instance.reset(new CSingleton);
});
return *m_instance.get();
}
好的,我明白了。但我认为 std::call_once
真正保证的唯一事情是传递的函数将 only 执行一次。但它是否也保证如果多个线程之间存在调用函数的竞赛,并且一个线程获胜,其他线程将阻塞直到获胜的线程从打电话?
因为如果是这样,我认为 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;
}
那么,如果 std::call_once
确实强制其他线程阻塞,那么 std::call_once
相对于常规互斥体有什么好处?再想一想,std::call_once
肯定会 强制其他线程阻塞,或者在用户提供的函数中完成的任何计算都不会同步。再说一遍,std::call_once
在普通互斥体之上提供了什么?
最佳答案
call_once
为您做的一件事是处理异常。也就是说,如果第一个线程在仿函数内抛出异常(并将其传播出去),call_once
将不会认为 call_once
满足。允许随后的调用再次进入仿函数,以无异常(exception)地完成它。
在您的示例中,异常(exception)情况也得到了妥善处理。然而,很容易想象一个更复杂的仿函数,其中的异常(exception)情况不会得到正确处理。
说了这么多,我注意到 call_once
对于函数局部静态是多余的。例如:
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
中,是相对于同一翻译单元中具有文件本地范围的其他对象构造的。使用 function-local-statics,在第一次执行 GetInstance
时构造 m_instance
。
这种差异对您的应用程序可能很重要,也可能不重要。一般来说,我更喜欢函数局部静态解决方案,因为它是“懒惰的”。 IE。如果应用程序从不调用 GetInstance()
,则永远不会构造 m_instance
。并且在应用程序启动期间没有一段时间试图一次构建大量静态。您只需在实际使用时支付 build 费用。
关于c++ - std::call_once vs std::mutex 用于线程安全初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26985370/