4

I have a collection that I am protecting with a mutex. After initialization it is only ever read, so I won't need a mutex there.

The collection is initialized and populated in global static initializers. I know that global static initialization is guaranteed within a single translation unit. Is there any guarantee that global static initialization will be single threaded?


I have a static collection that protected by a Schwarz counter and is populated by constructors of other static objects. The container is associated with a mutex. Given that the collection is read-only after main starts, I would like to get rid of the mutex if I can guarantee that static constructors are called in a single thread.

My understanding is that static initialization order is generally well defined within a single translation unit, but unspecified between translation units. Does the standard allow for the static objects to be initialized/constructed by different runtime provided threads?


Schwarz counter:

Header file:

struct Init
{
   Init();
   ~Init();
};
namespace
{
   Init _init;
}
extern std::map<int, std::unique_ptr<...>> &protected;

Source file:

namespace
{
   int init_count;
   std::aligned_storage<sizeof(std::map<int, std::unique_ptr<...>>), alignof(std::map<int, std::unique_ptr<...>>>)> protected_storage;
}
std::map<int, std::uniqe_ptr<...>> &protected = *reinterpret_cast<std::map<int, std::unique_ptr<...>> *>(&protected_storage);
Init::Init()
{
   if (!init_counter++)
   {
      new(&protected_storage) std::map<int, std::unique_ptr<...>>();
   }
}
Init::~Init()
{
   if (!--init_counter)
   {
      protected.~std::map<int, std::unique_ptr<...>>();
   }
}

Collection population:

struct helper
{
   helper(...)
   {
      protected.insert(std::make_pair(...));
   }
};

A macro is expanded that creates static instances of helper.

Graznarak
  • 3,626
  • 4
  • 28
  • 47
  • 2
    In C++11, static initialization does not introduce a data race. – Kerrek SB Aug 30 '13 at 23:29
  • Static initialization is either *zero initialization* or *constant initialization*. I don't think you could have any observable differences between multi-threaded and single-threaded *static* init (of non-local variables). – dyp Aug 30 '13 at 23:42
  • Are we really talking about global static initializers, or are we also considering constructors of static objects? – jxh Aug 30 '13 at 23:43
  • @DyP: Only the dynamic initialization phase is interesting. The static phase isn't "executing" and isn't ordered. – Kerrek SB Aug 31 '13 at 00:12
  • 1
    @KerrekSB Yes, but then what does your initial remark refer to? Dynamic initialization of variables with ordered initialization in different TUs can be *unsequenced* if the program starts a thread (therefore possible data race AFAIK). – dyp Aug 31 '13 at 00:23
  • @DyP: I was just being brief. I meant "the dynamic initialization phase of static initialization does not introduce a data race per se", though of course the initializing expressions themselves have to be sane and not, say, contend for shared data. – Kerrek SB Aug 31 '13 at 00:33
  • @KerrekSB Then I do not understand what you mean with *static initialization* :) I'm referring to the Standard, [basic.start.init]/2 "Together, zero-initialization and constant initialization are called *static initialization*; all other initialization is *dynamic initialization*." – dyp Aug 31 '13 at 00:36
  • 2
    @DyP: Ah OK, I meant (in the first comment) "initialization of objects with static storage duration". That was probably confusing. "Static" means too many things :-S – Kerrek SB Aug 31 '13 at 00:40
  • @Graznarak When you say "collection" do you mean array or STL container? Given the language lawyer discussion, it sounds important. It is very easy to have multiple threads running before main() starts running. – brian beuning Aug 31 '13 at 00:42
  • @Graznarak 1) Could you provide a code example of your Schwarz counter / population solution? 2) (dynamic) Initialization of "static"/global variables is either *indeterminately* sequenced or *unsequenced*, depending on your program. That is, yes, if your program starts a thread (while dynamic init is not yet completed), the Standard *does allow* concurrent initialization of e.g. global variables. It contains a note in [basic.start.init]/2: "This definition permits initialization of a sequence of ordered variables concurrently with another sequence." – dyp Sep 03 '13 at 02:10
  • @brianbeuning By "collection" I mean an STL container (std::map to be exact). – Graznarak Sep 03 '13 at 14:07
  • @DyP My program does not start any threads until after main has started. – Graznarak Sep 03 '13 at 14:20
  • I think `std::map> &protected = *reinterpret_cast/*...*/` is problematic, as the initialization of this reference is *dynamic initialization*, and not protected itself by the Schwarz counter. I.e. you Schwarz counter guarantees `protected_storage` is properly initialized, but it doesn't guarantee the reference is initialized before any access (at load-time, during dynamic init). – dyp Sep 03 '13 at 15:58
  • @DyP You may be right. Conceptually a reference is like a pointer, but uses value semantics. I was thinking that the reference would be initialized during static initialization and not dynamic initialization. – Graznarak Sep 03 '13 at 16:03
  • "My program does not start any threads until after main has started." Unfortunately, it's not that simple: Dynamic initialization can be deferred "to some point in time after the first statement of `main`" [basic.start.init]/4, (**but**, if that is the case, the init "shall occur before the first odr-use of any function or variable defined in the same translation unit as the variable to be initialized."). I'm not sure if your Schwarz counter helps with this, even if you used it in the TU which contains `main`. – dyp Sep 03 '13 at 16:12
  • @Graznarak Typically, a reference is initialized during *static initialization*, because it typically is initialized with a *constant expression*. However, the `reinterpret_cast` makes this initialization a non-constant expression. – dyp Sep 03 '13 at 16:13
  • @DyP It seems that initialization of static objects is more complicated than I realized. Unless you suggest otherwise, I intend to spend some time studying [basic.start.init] and then start another question to hopefully resolve any additional questions I come up with. – Graznarak Sep 03 '13 at 16:56
  • Sounds good, but what about the [solution](http://stackoverflow.com/a/18543786/420683) of [user1131467](http://stackoverflow.com/users/1131467/user1131467)? – dyp Sep 03 '13 at 17:01

1 Answers1

5

Is there any guarantee that global static initialization will be single threaded?

You mean dynamic initialization. No, single threaded initialization is explicitly not guaranteed.

From 3.6.2:

If a program starts a thread (30.3), the subsequent initialization of a variable is unsequenced with respect to the initialization of a variable defined in a different translation unit. Otherwise, the initialization of a variable is indeterminately sequenced with respect to the initialization of a variable defined in a different translation unit. If a program starts a thread, the subsequent unordered initialization of a variable is unsequenced with respect to every other dynamic initialization. Otherwise, the unordered initialization of a variable is indeterminately sequenced with respect to every other dynamic initialization

So if you start a thread in your program then two different global variables from two different TUs could theoretically have their constructors running at the same time from two different threads.

The best way to deal with these issues is to wrap your static storage duration variables as local static variables in the following "singleton pattern":

const T& f()
{
    static T t(a,b,c);
    return t;
}

The latest standard guarantees that the construction of t is thread-safe, so you will not need a mutex at all (at least not one explicitly specified, the compiler will generate the guard for you).

As an added benefit, the object is constructed "lazily" on the first call to f, so you don't need to worry about initialization order. If multiple such singletons call each other in their constructors (provided the dependencies are acyclic of course), they will be initialized in a working order. This is not the case for non-local variables.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319