darune
darune

Reputation: 11000

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.

Since this can be achieved by other means - when should std::call_once be used? What type of problems is it intended to address?

Please provide examples.

Upvotes: 32

Views: 24973

Answers (7)

Alex Guteniev
Alex Guteniev

Reputation: 13689

Other answers already mentioned that call_once effect may be achieved with magic static, or with mutex usage.

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). That's because mutex acquisition/release will be atomic RMWs, whereas call_once queries are just reads. You can fix mutex performance by implementing double-check lock, but this will complicate the program.

Using call_once is also safer than mutex, if the target function is first attempted to be called only on program exit. mutex isn't necessarily trivially destructible, so a global mutex may be destroyed by the time the target is to be called.

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.

MSVC specific: magic statics employ thread-local storage. This is more optimal than call_once. But magic static support can be turned off by /Zc:threadSafeInit-. The ability to turn it off is due to some thread-local storage disadvantages. call_once cannot be turned off. 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.

Upvotes: 0

Ben
Ben

Reputation: 9713

Caching and lazy evaluation. Suppose an immutable class has a cheap-to-store but expensive-to-compute property, double foo() const;. Rather than compute it on demand or compute it up front, you could do

private:
    mutable std::once_flag m_flag;
    mutable double m_foo;
    double doCalcFoo() const; // Expensive!
public:
    double foo() const {
        std::call_once(m_flag, [this] { m_foo = doCalcFoo(); });
        return m_foo;
    }

While you could do

private:
    mutable std::optional<double> m_foo;
    mutable std::mutex m_fooMutex;
    double doCalcFoo() const; // Expensive!
public:
    double foo() const {
        std::lock_guard lock{m_fooMutex};
        if (!m_foo) {
            m_foo = doCalcFoo();
        }
        return *m_foo;
    }

that's more bytes (40 + 16 = 56 bytes versus 4 + 8 + padding = 16 on Clang), less performance-optimal, and violates Parent's Better-Code goal of "No Raw Synchronization Primitives": (https://sean-parent.stlab.cc/presentations/2016-08-08-concurrency/2016-08-08-concurrency.pdf slide 6 through 11).

Upvotes: 6

Gabriel Ceron Viveros
Gabriel Ceron Viveros

Reputation: 41

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. 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).

#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;
  } 

};

Upvotes: -1

Caleth
Caleth

Reputation: 63019

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);

Upvotes: 1

Fabio
Fabio

Reputation: 2287

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.

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. 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.

Upvotes: 9

Fantastic Mr Fox
Fantastic Mr Fox

Reputation: 33904

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). How do we achieve this?

  1. Use a mutex? kind of ugly I guess.
  2. Use 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.

Upvotes: 6

The Quantum Physicist
The Quantum Physicist

Reputation: 26326

Example: I use it for libcURL to retrieve http(s) data from websites. In libcURL, you have to do a one-time global initialization before you're able to use the library. 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.

Upvotes: 34

Related Questions