![](/img/trans.png)
[英]Is it possible to capture return value of function used in std::call_once?
[英]std::call_once, when should it be used?
C++11 中引入的std::call_once
函数确保以线程安全的方式只调用一次可调用对象。
由于这可以通过其他方式实现 - 什么时候应该使用std::call_once
? 它打算解决什么类型的问题?
请举例说明。
示例:我将它用于 libcURL 以从网站检索 http(s) 数据。 在 libcURL 中,您必须先进行一次全局初始化,然后才能使用该库。 鉴于初始化不是线程安全的,但从网站请求数据是线程安全的,我使用call_once
只调用一次我的初始化,无论在哪个线程中以及是否并发调用。
想象一个带有一些巨大数据的单例实例(出于某种原因):
class Singleton {
public: static Singleton& get();
...
private: static std::unique_ptr<SingletonDataBase> instance;
}
我们如何确保 get 函数在正确调用时创建实例(无论出于何种原因,它都非常大并且不能进入静态内存空间)。 我们如何实现这一目标?
mutex
? 我猜有点丑。std::call_once
吗? 更好,并且坚定地给出了代码的意图:Singleton& Singleton::get() {
static std::once_flag flag;
std::call_once(flag, [&](){ instance.reset(new SingletonDataBase()); });
return instance.get_interface()
}
每当您需要只调用一次时,使用call_once
很好。
典型用途是当您希望在可能发生争用(多线程)的情况下按需初始化全局数据。
假设你有结构
struct A{ A() {/*do some stuff*/} };
并且你想要一个在全局范围内的实例。
如果您按如下方式进行,它会在 main 之前初始化,因此它不是按需的。
A a_global;
如果您按照以下方式进行操作,则它是按需提供的,但它不是线程安全的。
A *a_singleton = NULL;
A *getA() {
if (!a_singleton)
a_singleton = new A();
return a_singleton;
}
call_once
解决了这两个问题。 当然,您可以使用其他同步原语的某种组合来代替,但最终只会重新实现您自己的call_once
版本。
缓存和惰性求值。 假设一个不可变类有一个存储成本低但计算成本高的属性, double foo() const;
. 您不是按需计算或mutable std::once_flag m_flag; mutable double m_foo;
计算,而是添加mutable std::once_flag m_flag; mutable double m_foo;
mutable std::once_flag m_flag; mutable double m_foo;
然后可以做
double foo() const {
std::call_once(m_flag, [this] { m_foo = doCalcFoo(); });
return m_foo;
}
什么时候应该使用?
当你想调用一次时。 关于它在做什么,它简洁明了。
替代方案
struct CallFooOnce {
CallFooOnce() {
foo();
}
};
static CallFooOnce foo_once;
有更多的样板,并引入了一个额外的名称,超过
static std::once_flag foo_once;
std::call_once(foo_once, foo);
其他答案已经提到call_once
效果可以通过魔术静态或mutex
使用来实现。
我想专注于性能和极端情况处理。
在快速路径上使用mutex
会比call_once
慢(当目标函数已经被调用时)。 那是因为mutex
获取/释放将是原子 RMW,而call_once
查询只是读取。 您可以通过实施双重检查锁定来修复mutex
性能,但这会使程序复杂化。
如果第一次尝试仅在程序退出时调用目标函数,则使用call_once
也比 mutex 更安全。 mutex
不一定可以简单地破坏,因此在调用目标时可能会破坏全局互斥体。
通过只读取查询对象是否已经初始化,魔术静态也可能在快速路径上很快。 magic static 与call_once
的性能可能取决于 implementation ,但通常 magic static 有更多的优化机会,因为它不必在变量中公开状态并且必须由编译器实现,而不仅仅是在库中.
MSVC 特定:魔术静态使用线程本地存储。 这比call_once
更优化。 但是可以通过/Zc:threadSafeInit-关闭魔法静态支持。 关闭它的能力是由于一些线程本地存储的缺点。 call_once
不能关闭。 在 Visual Studio 2015 之前, call_once
没有很好地实现,并且根本没有实现魔术静态,因此如果没有 3rd 方库,很难获得一次性初始化。
std::call_once()
可用于惰性求值,这样传递给它的可调用对象即使有多个线程执行,也只会执行一次。 请参阅下一个示例,其中getInstance()
方法可能会被多个线程多次调用,但是该实例仅在需要时创建一次(第一次调用 getInstance())。
#include <mutex>
class Singleton {
static Singleton *instance;
static std::once_flag inited;
Singleton() {...} //private
public:
static Singleton *getInstance() {
std::call_once( inited, []() {
instance=new Singleton();
});
return instance;
}
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.