0

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

codesavesworld
  • 587
  • 3
  • 10
  • "Every thread will call `Init()` before `Foo()`: except for the one that doesn't. This is a bug waiting to happen. – Pete Becker Jul 06 '22 at 12:03

1 Answers1

1

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.

dewaffled
  • 2,850
  • 2
  • 17
  • 30
  • so if I add initialized check at the beginning of other functions , this approach has the same effect as the one using call_once and once_flag right? – codesavesworld Jul 06 '22 at 09:41
  • and I'm kind of confused that why it is not always possible to add initialized check – codesavesworld Jul 06 '22 at 09:45
  • 1
    @zenxy no the approach you try to use is not good. It may happen that `x.Init(); x.Foo();` will not really run `Foo();` because `initialized` is still false as another thread is in the process of finishing the initialization. – ALX23z Jul 06 '22 at 09:50
  • @ALX23z yes, currently when I use this approach, if other thread is call Foo() before Init() is finished, Foo will simply print error message and return. If one thread is in the process of call_once and the other thread come, will the later thread wait call_once finish? – codesavesworld Jul 06 '22 at 09:57
  • 2
    @codesavesworld -- yes, the second thread will block until `call_once` finishes. That's the point! – Pete Becker Jul 06 '22 at 12:05