Is std :: call_once reentrant and thread safe?

std :: call_once is thread safe, but is it also repetitive?

My testing using VS2012 (Debug and Release) showed that calling std::call_once recursive from one thread in order, but if calls are made in separate threads, it will cause a deadlock. Is this a known limitation of std::call_once ?

 #include "stdafx.h" #include <iostream> #include <mutex> #include <thread> void Foo() { std::cout << "Foo start" << std::endl; std::once_flag flag; std::call_once( flag, [](){ std::cout << "Hello World!" << std::endl; }); std::cout << "Foo end" << std::endl; } int _tmain(int argc, _TCHAR* argv[]) { // Single threaded Works { std::once_flag fooFlag; std::call_once( fooFlag, Foo); } // Works // Threaded version, join outside call_once { std::once_flag fooFlag; std::thread t; std::call_once( fooFlag, [&t](){ t = std::thread(Foo); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); }); t.join(); } // Dead locks // Threaded version, join inside call_once { std::once_flag fooFlag; std::call_once( fooFlag, [](){ auto t = std::thread(Foo); t.join(); }); } return 0; } 

It seems that std:call_once blocking the static mutex, which will not unlock until the function exits. In the single-threaded case, this works because in the second call this thread already has a lock. In the streaming version, it will be blocked until the first call.

I also noticed that if you change the std::once_flag in Foo() to static , a deadlock will happen anyway.

+6
source share
2 answers

The closest standard points to this 17.6.5.8 [reentrancy] :

1 - Except as otherwise explicitly specified in this standard, it is defined by an implementation whose functions in the C ++ standard library can be recursively returned.

Unfortunately, the call_once specification call_once not say whether it is recursive (or cross-draw recursive), and the preamble of the thread support library does not say anything about this either.

Nevertheless, the implementation of VC ++ is clearly suboptimal, especially since you can write a version of userland call_once using condition_variable :

 #include <mutex> #include <condition_variable> struct once_flag { enum { INIT, RUNNING, DONE } state = INIT; std::mutex mut; std::condition_variable cv; }; template<typename Callable, typename... Args> void call_once(once_flag &flag, Callable &&f, Args &&...args) { { std::unique_lock<std::mutex> lock(flag.mut); while (flag.state == flag.RUNNING) { flag.cv.wait(lock); } if (flag.state == flag.DONE) { return; } flag.state = flag.RUNNING; } try { f(args...); { std::unique_lock<std::mutex> lock(flag.mut); flag.state = flag.DONE; } flag.cv.notify_all(); } catch (...) { { std::unique_lock<std::mutex> lock(flag.mut); flag.state = flag.INIT; } flag.cv.notify_one(); throw; } } 

Note that this is a small-scale implementation; you can also write a coarse-grained implementation that uses one pair of mutex and a condition variable for all flags, but then you need to make sure that you notify all waiting threads when throwing exceptions (for example, libC ++ does this).

For efficiency, you can make once_flag::state atomic and use a double-check lock; this is omitted here for brevity.

+6
source

Not the β€œanswer” here, but at least the confirmation that something seems to be wrong on the side of Visual Studio compared to your overall usage pattern.

I compiled and ran this example on coliru.stacked-crooked.com and seemed to execute it, as expected, without any deadlock.

Here's the compilation line:

 g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out 

Here's the conclusion:

 Foo start Hello World! Foo end Foo start Hello World! Foo end Foo start Hello World! Foo end 

I personally believe that there are problems with my own code before I blame the library implementation.

I saw that there are some similar problems there wrt Visual Studio call_once implementation:

https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web& cd = 3 & Ved = 0CDwQFjAC & URL = HTTPS% 3A% 2F% 2Fconnect.microsoft.com% 2FVisualStudio% 2Ffeedback% 2Fdetails % 2F811192% 2Fstd-call of one-time freeze & e = cVw0U-rZBor-2gXCh4HYDw ​​& USG = AFQjCNGDikvgq8YHVzWLC- BqjeOSN-Vm3Q & Sig2 = 7BtB2jH8DeTKEwbVA.6 & bvAv6A3B8V3Av6Av3Av6Av3Av6Av3Av2Av3Av4Av3Av3Av3

http://www.unknownerror.org/Problem/index/1100762158/why-does-this-c-static-singleton-never-stop/

I would be interested to know if this problem occurs in Debug or Release, or both?

The ISO standard states that call_once is thread safe, after all!

0
source

All Articles