繁体   English   中英

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 函数在正确调用时创建实例(无论出于何种原因,它都非常大并且不能进入静态内存空间)。 我们如何实现这一目标?

  1. 使用mutex 我猜有点丑。
  2. 使用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使用来实现。

我想专注于性能和极端情况处理。

在快速路径上使用mutexcall_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM