5

I'm using std::call_once in my code to initialize some shared variables only once. The calling code is inside a callback that is triggered by multiple threads. What I'm interested to know, since I couldn't find it in the documentation is whether std::call_once is blocking essentially as if there was a std::lock_guard instead? In practice it looks like this is the case.

For example, the following will print "Done" before any print() will be called:

#include <future>
#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;

void print()
{
    for(int i=0;i<10;i++)
    {
          std::cout << "Hi, my name is " << std::this_thread::get_id() 
            << ", what?" << std::endl;
    }
}

void do_once()
{
    std::cout << "sleeping for a while..." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    std::cout << "Done" << std::endl;
}

void work()
{
    std::call_once(flag, [](){ do_once(); });
    print();
}


int main()
{
    auto handle1 = std::async(std::launch::async, work);
    auto handle2 = std::async(std::launch::async, work);
    auto handle3 = std::async(std::launch::async, work);
    auto handle4 = std::async(std::launch::async, work);

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}

I'm assuming this is indeed the case (since I don't see how it could be implemented otherwise), but is this behavior guaranteed or could there be a compiler that decides that std::call_once will indeed be called once but allow other threads to continue and just ignore this call?

ZivS
  • 2,094
  • 2
  • 27
  • 48
  • 1
    _<...>since I couldn't find it in the documentation<...>_ What? It's the first sentence in the [documentation](http://en.cppreference.com/w/cpp/thread/call_once): _Executes the Callable object f exactly once, **even if called from several threads**._ – Algirdas Preidžius Mar 27 '17 at 19:21
  • 1
    @AlgirdasPreidžius, I'm not a code lawyer but this doesn't mean that other threads must wait for the callable to complete before advancing to next line of code – ZivS Mar 27 '17 at 19:22
  • 4
    "No invocation in the group returns before the above-mentioned execution of the selected function is completed successfully, that is, doesn't exit via an exception." – T.C. Mar 27 '17 at 19:23
  • Not quite a dup, but insightful enough: http://stackoverflow.com/questions/40707149/is-stdcall-once-lock-free – Potatoswatter Mar 27 '17 at 19:25
  • @T.C., I'm having trouble understanding that sentence, could you make this into an answer and explain what group? what is the above-mentioned execution"? – ZivS Mar 27 '17 at 19:29
  • `No invocation in the group returns before the above-mentioned execution of the selected function is completed successfully, that is, doesn't exit via an exception.` It is guaranteed to block until one of the functions passed to `call_once` with the same `once_flag` is done executing. – David Mar 27 '17 at 19:39

1 Answers1

5

Yes std::call_once is a blocking call. From [thread.once.callonce] we have

Effects: An execution of call_once that does not call its func is a passive execution. An execution of call_once that calls its func is an active execution. An active execution shall call INVOKE (DECAY_COPY ( std::forward<Callable>(func)), DECAY_COPY (std::forward<Args>(args))...). If such a call to func throws an exception the execution is exceptional, otherwise it is returning. An exceptional execution shall propagate the exception to the caller of call_once. Among all executions of call_once for any given once_flag: at most one shall be a returning execution; if there is a returning execution, it shall be the last active execution; and there are passive executions only if there is a returning execution. [ Note: passive executions allow other threads to reliably observe the results produced by the earlier returning execution. —end note ]

Synchronization: For any given once_flag: all active executions occur in a total order; completion of an active execution synchronizes with (1.10) the start of the next one in this total order; and the returning execution synchronizes with the return from all passive executions.

emphasis mine

This means that all calls to call_once will wait until the function passed to call_once completes. In your case that means do_once() must be called before any thread calls print()

NathanOliver
  • 171,901
  • 28
  • 288
  • 402