简体   繁体   English

std::call_once,什么时候用?

[英]std::call_once, when should it be used?

The std::call_once function , introduced in C++11, ensures a callable is called exactly one time, in a thread safe manner. C++11 中引入的std::call_once函数确保以线程安全的方式只调用一次可调用对象。

Since this can be achieved by other means - when should std::call_once be used?由于这可以通过其他方式实现 - 什么时候应该使用std::call_once What type of problems is it intended to address?它打算解决什么类型的问题?

Please provide examples.请举例说明。

Example: I use it for libcURL to retrieve http(s) data from websites.示例:我将它用于 libcURL 以从网站检索 http(s) 数据。 In libcURL, you have to do a one-time global initialization before you're able to use the library.在 libcURL 中,您必须先进行一次全局初始化,然后才能使用该库。 Given that initialization is not thread-safe , but requesting data from websites is thread-safe, I use call_once that calls my initialization only once, no matter in what thread and whether it's called concurrently.鉴于初始化不是线程安全的,但从网站请求数据是线程安全的,我使用call_once只调用一次我的初始化,无论在哪个线程中以及是否并发调用。

Imagine a singleton instance with some giant data (for some reason):想象一个带有一些巨大数据的单例实例(出于某种原因):

class Singleton {
    public:  static Singleton& get();
    ...
    private: static std::unique_ptr<SingletonDataBase> instance;
}

How can we insure that the get function, when called correctly creates the instance ( which for whatever reason is really large and can't go in static memory space ).我们如何确保 get 函数在正确调用时创建实例(无论出于何种原因,它都非常大并且不能进入静态内存空间)。 How do we achieve this?我们如何实现这一目标?

  1. Use a mutex ?使用mutex kind of ugly I guess.我猜有点丑。
  2. Use std::call_once ?使用std::call_once吗? Nicer, and firmly gives the intention of the code:更好,并且坚定地给出了代码的意图:

Singleton& Singleton::get() {
    static std::once_flag flag;
    std::call_once(flag, [&](){ instance.reset(new SingletonDataBase()); });
    return instance.get_interface()
}

Whenever you need to call something exactly once, its nice to use call_once .每当您需要只调用一次时,使用call_once很好。

The typical use is when you want to initialize a global piece of data on-demand in a situation of possible contention (multi-threading).典型用途是当您希望在可能发生争用(多线程)的情况下按需初始化全局数据。

Suppose you have the struct假设你有结构

struct A{ A() {/*do some stuff*/} };

and you want an instance of it in global scope.并且你想要一个在全局范围内的实例。

If you do as below, it gets initialized before main, so it is not on-demand.如果您按如下方式进行,它会在 main 之前初始化,因此它不是按需的。

A a_global;

If you do as below, then it is on demand but it is not thread safe.如果您按照以下方式进行操作,则它是按需提供的,但它不是线程安全的。

A *a_singleton = NULL;
A *getA() { 
   if (!a_singleton)
      a_singleton = new A();
   return a_singleton;
}

call_once solves these two problems. call_once解决了这两个问题。 Of course you can use some combination of other synchronization primitives instead, but you would just end up re-implementing your own version of call_once .当然,您可以使用其他同步原语的某种组合来代替,但最终只会重新实现您自己的call_once版本。

Caching and lazy evaluation.缓存和惰性求值。 Suppose an immutable class has a cheap-to-store but expensive-to-compute property, double foo() const;假设一个不可变类有一个存储成本低但计算成本高的属性, double foo() const; . . Rather than compute it on demand or compute it up front, you add 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; mutable std::once_flag m_flag; mutable double m_foo; and then can do然后可以做

double foo() const {
    std::call_once(m_flag, [this] { m_foo = doCalcFoo(); });
    return m_foo;
}

When should it be used ?什么时候应该使用?

When you want to call something once.当你想调用一次时。 It is concise and clear as to what it is doing.关于它在做什么,它简洁明了。

The alternative替代方案

struct CallFooOnce { 
    CallFooOnce() { 
        foo(); 
    } 
}; 
static CallFooOnce foo_once;

has much more boilerplate, and introduces an additional name, over有更多的样板,并引入了一个额外的名称,超过

static std::once_flag foo_once;
std::call_once(foo_once, foo);

Other answers already mentioned that call_once effect may be achieved with magic static, or with mutex usage.其他答案已经提到call_once效果可以通过魔术静态或mutex使用来实现。

I want to focus on performance and corner case handling.我想专注于性能和极端情况处理。

Using mutex will be slower than call_once on fast path (when the target function is already called).在快速路径上使用mutexcall_once(当目标函数已经被调用时)。 That's because mutex acquisition/release will be atomic RMWs, whereas call_once queries are just reads.那是因为mutex获取/释放将是原子 RMW,而call_once查询只是读取。 You can fix mutex performance by implementing double-check lock, but this will complicate the program.您可以通过实施双重检查锁定来修复mutex性能,但这会使程序复杂化。

Using call_once is also safer than mutex , if the target function is first attempted to be called only on program exit.如果第一次尝试仅在程序退出时调用目标函数,则使用call_once也比 mutex 更安全 mutex isn't necessarily trivially destructible, so a global mutex may be destroyed by the time the target is to be called. mutex不一定可以简单地破坏,因此在调用目标时可能会破坏全局互斥体。

Magic static is also likely to be fast on fast path by making only reads to query if the object is already initialized.通过只读取查询对象是否已经初始化,魔术静态也可能在快速路径上很快。 The performance of magic static vs call_once may depend on implementation , but generally magic static has more opportunity for the optimization, as it doesn't have to expose the state in a variable and has to be implemented by the compiler rather than only in the library. magic static 与call_once的性能可能取决于 implementation ,但通常 magic static 有更多的优化机会,因为它不必在变量中公开状态并且必须由编译器实现,而不仅仅是在库中.

MSVC specific: magic statics employ thread-local storage. MSVC 特定:魔术静态使用线程本地存储。 This is more optimal than call_once .这比call_once更优化。 But magic static support can be turned off by /Zc:threadSafeInit- .但是可以通过/Zc:threadSafeInit-关闭魔法静态支持。 The ability to turn it off is due to some thread-local storage disadvantages.关闭它的能力是由于一些线程本地存储的缺点。 call_once cannot be turned off. call_once不能关闭。 Before Visual Studio 2015, call_once wasn't implemented well, and magic statics weren't implemented at all, so it was hard to get one-time initialization without 3rd party libs.在 Visual Studio 2015 之前, call_once没有很好地实现,并且根本没有实现魔术静态,因此如果没有 3rd 方库,很难获得一次性初始化。

std::call_once() can be used for Lazy Evaluation, in the way that the callable object passed to it will be executed only one time even if many threads execute it. std::call_once()可用于惰性求值,这样传递给它的可调用对象即使有多个线程执行,也只会执行一次。 See the next example, where the method getInstance() may be called many times and by many threads, however the instance is created only one time and at the moment of needing it (call getInstance() for the first time).请参阅下一个示例,其中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