Thread-safe "Nifty Counter" (aka, "Schwarz Counter")

Viewed 170

The code for a "Nifty Counter" (aka, a "Schwarz Counter")

// Stream.h
#ifndef STREAM_H
#define STREAM_H

struct Stream {
  Stream ();
  ~Stream ();
};
extern Stream& stream; // global stream object

static struct StreamInitializer {
  StreamInitializer ();
  ~StreamInitializer ();
} streamInitializer; // static initializer for every translation unit

#endif // STREAM_H
// Stream.cpp
#include "Stream.h"

#include <new>         // placement new
#include <type_traits> // aligned_storage

static int nifty_counter; // zero initialized at load time
static typename std::aligned_storage<sizeof (Stream), alignof (Stream)>::type
  stream_buf; // memory for the stream object
Stream& stream = reinterpret_cast<Stream&> (stream_buf);

Stream::Stream ()
{
  // initialize things
}
Stream::~Stream ()
{
  // clean-up
} 

StreamInitializer::StreamInitializer ()
{
  if (nifty_counter++ == 0) new (&stream) Stream (); // placement new
}
StreamInitializer::~StreamInitializer ()
{
  if (--nifty_counter == 0) (&stream)->~Stream ();
}

seems like it would be thread-safe... except for this:

All non-local variables with static storage duration are initialized as part of program startup, before the execution of the main function begins ...

So far, so good.

(unless deferred, see below).

Uh oh.

It is implementation-defined whether dynamic initialization happens-before the first statement of the main function (for statics) or the initial function of the thread (for thread-locals), or deferred to happen after.

If the dynamic initialization of at the first Nifty counter static object is deferred to happen after the first statement of the main function and the first statement of the main function creates a thread and that thread accesses stream, how can it be guaranteed that the stream object will have been fully initialized in the main thread before it's accessed in the second thread?

Does that code contain a race condition? If so, how would you fix it? Use reader/writer locks?


EDIT: making the counter atomic does not address the issue. Even if the counter is atomic, that doesn't guarantee that the constructor has completed on the original thread before the object is accessed on the second thread. Unlike function-local static constructors that are guaranteed to be thread-safe, there's no such guarantee for constructors called via placement new.

0 Answers
Related