Function-scope static initialization C++11 thread-safe guard variance across compilers
0 reputation · 18 Dec 2020, 08:46 UTC
0 reputation · 18 Dec 2020, 08:46 UTC
Function-scope static variables are required by C++11 and later to be initialized in a thread-safe manner. The Standard mandates the single-initialization guarantee but does not specify the synchronization mechanism, leaving the lock type and algorithm to the implementation.
The feature is relevant for code paths hit by many concurrent requests where the first entry to a function triggers initialization. Because the guard is implementation-defined, the cost and contention profile can differ between compilers and standard library configurations, creating an unresolved design decision for latency-sensitive code.
What synchronization primitive is used to guard first-time initialization of a function-scope static in a specific compiler and library combination? How does that choice influence contention when multiple threads concurrently enter the same function for the first time? Is there a portable way to reason about the initialization cost without implementation-specific inspection?
34775 reputation · 18 Dec 2020, 09:46 UTC
You cannot know the synchronization primitive portably. C++11 and later require thread-safe one-time initialization of function-scope statics with a single-initialization guarantee and happens-before publication, but the Standard does not specify how the guard is implemented. The choice of primitive and its contention profile is implementation-defined and can change with compiler, standard library, platform and release.
Implementations typically use a per-static guard variable. The common pattern is a fast atomic fast-path that checks an "already initialized" state for steady-state calls, and a slower synchronization operation on first entry. The first-entry path is likely to involve an atomic compare-exchange plus a heavier primitive such as a mutex-like lock or a once primitive provided by the platform. Exact choices differ by implementation.
When multiple threads enter the same function for the first time they contend on that guard. The first thread performs the initialization while others wait on the implementation's wait primitive. This creates a serializing spike whose cost depends on the chosen primitive, its fairness and the work done inside the initializer. Do not assume lock-free or zero-cost first entry.
Treat first-time function-scope static initialization as potentially expensive and serializing. Portable design guidance is:
To narrow the implementation class you need one missing diagnostic detail: compiler name, version, standard library and target OS/arch for the build you care about. With that, you can inspect the binary symbols and generated code around the function for guard acquisition routines and measure the first-call contention on that exact configuration.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.