I'm a bit confused about the purpose of std::call_once
. To be clear, I understand exactly what std::call_once
does, and how to use it. It's usually used to atomically initialize some state, and make sure that only one thread initializes the state. I've also seen online many attempts to create a thread-safe singleton with std::call_once
.
As demonstrated here, suppose you write a thread safe singleton, as such:
CSingleton& CSingleton::GetInstance()
{
std::call_once(m_onceFlag, [] {
m_instance.reset(new CSingleton);
});
return *m_instance.get();
}
Okay, I get the idea. But I thought that the only thing std::call_once
really guarantees is that the passed function will only be executed once. But does it also guarantee that if there is a race to call the function between multiple threads, and one thread wins, the other threads will block until the winning thread returns from the call?
Because if so, I see no difference between call_once
and a plain synchronization mutex, like:
CSingleton& CSingleton::GetInstance()
{
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_instance)
{
m_instance.reset(new CSingleton);
}
lock.unlock();
return *m_instance;
}
So, if std::call_once
indeed forces other threads to block, then what benefits does std::call_once
offer over a regular mutex? Thinking about it some more, std::call_once
would certainly have to force the other threads to block, or whatever computation was accomplished in the user-provided function wouldn't be synchronized. So again, what does std::call_once
offer above an ordinary mutex?
The CPP Reference states std::call_once is thread safe: Executes the function f exactly once, even if called from several threads.
There are three ways in C++ to initialize variables in a thread safe way.
Thread-safe locking, the right way_map. end()) auto = -> return return ::<>(); // auto unlock (lock_guard, RAII) void insertint ::<CacheData> value) ::<::> l(_mtx);
std::call_once ensures execution of a function exactly once by competing threads. It throws std::system_error in case it cannot complete its task.
One thing that call_once
does for you is handle exceptions. That is, if the first thread into it throws an exception inside of the functor (and propagates it out), call_once
will not consider the call_once
satisfied. A subsequent invocation is allowed to enter the functor again in an effort to complete it without an exception.
In your example, the exceptional case is also handled properly. However it is easy to imagine a more complicated functor where the exceptional case would not be properly handled.
All this being said, I note that call_once
is redundant with function-local-statics. E.g.:
CSingleton& CSingleton::GetInstance()
{
static std::unique_ptr<CSingleton> m_instance(new CSingleton);
return *m_instance;
}
Or more simply:
CSingleton& CSingleton::GetInstance()
{
static CSingleton m_instance;
return m_instance;
}
The above is equivalent to your example with call_once
, and imho, simpler. Oh, except the order of destruction is very subtly different between this and your example. In both cases m_instance
is destroyed in reverse order of construction. But the order of construction is different. In your m_instance
is constructed relative to other objects with file-local scope in the same translation unit. Using function-local-statics, m_instance
is constructed the first time GetInstance
is executed.
That difference may or may not be important to your application. Generally I prefer the function-local-static solution as it is "lazy". I.e. if the application never calls GetInstance()
then m_instance
is never constructed. And there is no period during application launch when a lot of statics are trying to be constructed at once. You pay for the construction only when actually used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With