In book C Concurrency in Action 2nd, 3.3.1, the author introduced a way using call_once
function to avoid double-checked locking pattern when doing initialization in multi-thread program,
std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag;
void init_resource()
{
resource_ptr.reset(new some_resource);
}
void foo()
{
std::call_once(resource_flag,init_resource); #1
resource_ptr->do_something();
}
the reason is explained in this [answer][1]. I used to use atomic_flag
to do initialization in multi-thread program, something like this:
td::atomic_flag init = ATOMIC_FLAG_INIT;
std::atomic<bool> initialized = false;
void Init()
{
if (init.test_and_set()) return;
DoInit();
initialized = true;
}
void Foo(){
if(!initialized) return;
DoSomething(); // use some variable intialized in DoInit()
}
every threads will call Init()
before call Foo()
.
After read the book, I wonder will the above pattern cause race condition, therefore not safe to use? Is it possible that the compiler reorder the instructions and initialized
become true before DoInit()
finish?
[1]: Explain race condition in double checked locking
CodePudding user response:
The race condition in your code happens when thread 1 enters DoInit
and thread 2 skips it and proceeds to Foo
.
You handle it with if(!initialized) return
in Foo
but this is not always possible: you should always expect a method to accidently do nothing and you can forget to add such checks to other methods.
With std::call_once
with concurrent invocation the execution will only continue after the action is executed.
Regarding reordering, atomics operations use memory_order_seq_cst by default that does not allow any reordering.