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