Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::call_once vs std::mutex for thread-safe initialization

Tags:

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?

like image 959
Siler Avatar asked Nov 18 '14 02:11

Siler


People also ask

Is std :: Call_once thread safe?

The CPP Reference states std::call_once is thread safe: Executes the function f exactly once, even if called from several threads.

How many ways are there in c++ for initializing variables in a thread safe way?

There are three ways in C++ to initialize variables in a thread safe way.

How to make class thread safe in c++?

Thread-safe locking, the right way_map. end()) auto = -> return return ::<>(); // auto unlock (lock_guard, RAII) void insertint ::<CacheData> value) ::<::> l(_mtx);

What is std :: Call_once?

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.


1 Answers

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.

like image 118
Howard Hinnant Avatar answered Nov 13 '22 05:11

Howard Hinnant