std::call_once is thread safe, but is it re-entrant as well?
My testing using VS2012 (Debug & Release) has shown that calling std::call_once
recursively from a single thread is okay, but if the calls are made on 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 like std:call_once
is locking a static mutex that doesn't get unlocked until the function exits. In the single-threaded case it works because on the second call that thread already has the lock. On the threaded version it will block until the first call exits.
I also noticed that if you change the std::once_flag
flag in the Foo()
function to static
that the deadlock will still occur.
The CPP Reference states std::call_once is thread safe: Executes the function f exactly once, even if called from several threads.
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.
The closest the standard comes to specifying this is 17.6.5.8 [reentrancy]:
1 - Except where explicitly specified in this standard, it is implementation-defined which functions in the Standard C ++ library may be recursively reentered.
Unfortunately the specification of call_once
doesn't say whether it is recursive (or cross-thread recursive), and the thread support library preamble doesn't say anything on this topic either.
That said, the VC++ implementation is clearly suboptimal, especially as it's possible to write a userland version of 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 fine-grained implementation; it's also possible to write a coarse-grained implementation that uses a single pair of mutex and condition variable across all once flags, but then you need to ensure that you notify all the waiting threads on throwing an exception (for example, libc++ does this).
For efficiency you could make once_flag::state
an atomic and use double-checked locking; this is omitted here for conciseness.
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