Another pattern for using the InitOnce functions

Raymond Chen

Raymond

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

0 comments

Comments are closed. Login to edit/delete your existing comments