Home > OS >  multi-thread program initialization using call_once vs atomic_flag
multi-thread program initialization using call_once vs atomic_flag

Time:07-07

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.

  • Related