Another pattern for using the InitOnce functions


In my survey of patterns for using the InitOnce functions, I omitted the synchronous two-phase initialization.

The synchronous two-phase initialization is similar to the simple callback-based version in that only one thread gets to attempt an initialization at a time. But instead of doing the initialization in a callback, you do the initialization inline.

As a refresher, here's how you do it using Init­Once­Execute­Once:

BOOL CALLBACK AllocateAndInitializeTheThing(
    PINIT_ONCE initOnce,
    PVOID parameter,
    PVOID *context)
{
    *context = new(std::nothrow) Thing();
    return *context != nullptr;
}

Thing *GetSingletonThing()
{
    static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
    void *result;
    if (InitOnceExecuteOnce(&initOnce,
                            AllocateAndInitializeTheThing,
                            nullptr, &result)) {
        return static_cast<Thing*>(result);
    }
    return nullptr;
}

To use Init­Once­Begin­Initialize in synchronous mode, you basically move the callback function inline:

Thing *GetSingletonThing()
{
    static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT;
    void *result;
    BOOL pending;
    if (InitOnceBeginInitialize(&initOnce, 0,
                                &pending, &result)) {
        if (pending) {
            // Try to initialize the thing.
            result = new(std::nothrow) Thing();

            InitOnceComplete(&initOnce,
                result ? 0 : INIT_ONCE_INIT_FAILED,
                result);
        }
        return static_cast<Thing*>(result);
    }
    return nullptr;
}

You start by calling Init­Once­Begin­Initialize, and the value stored in the pending parameter tells you whether you need to run the initialization. If it says that you need to initialize, then do your initialization and then report the result back by calling Init­Once­Complete, saying either 0 to mean that initialization succeeded, or INIT_ONCE_INIT_FAILED to say that it failed.

If a second thread tries to initialize while an initialization is already in progress, the initial request waits to see what the result of the existing initialization is. If the existing initialization eventually succeeds, then the second initialization is told, "It's all good. No need to initialize." If the existing initialization eventually fails, then the second initialization is told, "Not yet initialized. Why don't you give it a shot?"

In other words, Init­Once­Execute­Once acts like a wrapper that goes roughly like this:

BOOL InitOnceExecuteOnce(
    PINIT_ONCE initOnce,
    PINIT_ONCE_FN callback,
    void* parameter,
    void** context)
{
  BOOL pending;
  BOOL success = InitOnceBeginInitialize(
                          initOnce, 0, &pending, context)) {
  if (success) {
    if (pending) {
      success = callback(initOnce, parameter, context);
      InitOnceComplete(initOnce,
        success ? 0 : INIT_ONCE_INIT_FAILED, *context);
    }
  }
  return success;
}

Here's a comparison table:

Init­Once­Execute­Once Init­Once­Begin­Initialize
Synchronous mode
Init­Once­Begin­Initialize
Asynchronous mode
How initialized Callback Inline Inline
Initialization parallelism Serialized Serialized Parallel
Success reporting Callback returns TRUE Init­Once­Complete(0) Init­Once­Complete(INIT_ONCE_ASYNC)
Failure reporting Callback returns FALSE Init­Once­Complete(INIT_ONCE_FAILED) Do nothing
Comments (1)
  1. kantos says:

    A side note: std::call_once[1] appears roughly equivalent to the synchronous path. Magic statics[2] may be faster as the compiler can optimize quite a bit but most likely cannot handle two phase init safely as shown above. C++ does not appear to provide an equivalent to the asynchronous pattern.

    [1] http://en.cppreference.com/w/cpp/thread/call_once
    [2] http://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables

Comments are closed.

Skip to main content