Std :: call_once vs std :: mutex for initialization without threads

I am a bit confused about the purpose of std::call_once . To be clear, I understand exactly what std::call_once , and how to use it. It is usually used to atomically initialize a state and ensures that only one thread initializes the state. I have also seen many attempts on the Internet to create a thread safe singleton with std::call_once .

As shown , 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(); } 

OK I understood. But I thought the only thing std::call_once really guarantees was that the function passed would only be executed once. But does this also guarantee that if there is a race to call a 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 do not see the difference between call_once and the simple synchronization mutex, for example:

 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 really makes it block other threads, then what are the advantages of std::call_once over a regular mutex? After thinking about this a bit more, std::call_once would undoubtedly have to make other threads block or whatever the calculation in the function provided by the user would be synchronized. So what does std::call_once over a regular mutex?

+6
source share
2 answers

One thing call_once does for you is handle exceptions. That is, if the first thread into it throws an exception inside the functor (and propagates it), call_once will not satisfy call_once . A subsequent call is allowed to enter the functor again, trying to complete it without exception.

In your example, the exception case is also handled properly. However, it is easy to imagine a more complex functor where the exceptional case will not be properly handled.

All this suggests that call_once redundant with the call_once -statics function. For instance:.

 CSingleton& CSingleton::GetInstance() { static std::unique_ptr<CSingleton> m_instance(new CSingleton); return *m_instance; } 

Or easier:

 CSingleton& CSingleton::GetInstance() { static CSingleton m_instance; return m_instance; } 

The above is equivalent to your call_once example, and imho is simpler. Oh, apart from the order of destruction, it is very subtly different from this and your example. In both cases, m_instance destroyed in the reverse order of construction. But the construction order is different. In your m_instance built in relation to other objects with a local-local area in the same translation unit. Using the m_instance -statics function, m_instance is created when m_instance is executed.

This difference may or may not be important for your application. I usually prefer the function-locally-static solution, as it is "lazy". That is, if the application never calls GetInstance() , then m_instance never created. And there is no time during the launch of the application, when many statics are trying to build right away. You pay for the construction only in actual use.

+11
source

<y> If you read this , you will see that std::call_once does not guarantee any data frames, it is just a utility function to execute an action once (which will work through threads). You should not assume that there is anything close to the influence of a mutex.

as an example:

 #include <thread> #include <mutex> static std::once_flag flag; void f(){ operation_that_takes_time(); std::call_once(flag, [](){std::cout << "f() was called\n";}); } void g(){ operation_that_takes_time(); std::call_once(flag, [](){std::cout << "g() was called\n";}); } int main(int argc, char *argv[]){ std::thread t1(f); std::thread t2(g); t1.join(); t2.join(); } 

can print both f() was called and g() was called . This is because in the body of std::call_once it will check if flag was set, and then set it, if not, then call the corresponding function. But while it checks or before setting the flag , another thread can call call_once with the same flag and simultaneously run the function. You should still protect call_once calls with the mutex if you know that another thread may have a data race. C>

EDIT

I found a suggestion link for the std::call_once and a thread library that states that concurrency is guaranteed to call only once, so it should work like a mutex (y)

More specific:

If several calls to call_once with the same flag are made simultaneously in separate threads, then only one thread must call func, and the thread will not continue until the func function call is completed.

So, to answer your question: yes, other threads will be blocked until the calling thread returns from the specified functor.

+1
source

Source: https://habr.com/ru/post/978312/


All Articles