Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of initialization and destruction of block-scope static vs. namespace-scope thread_local in main thread

I'm trying to understand the sequencing rules for initialization and destruction of namespace-scope and block-scope objects with static storage duration and thread-local storage duration in the context of the main thread. Consider these two classes:

struct Foo {
    Foo() { std::cout << "Foo\n"; }
    ~Foo() { std::cout << "~Foo\n"; }
    static Foo &instance();
};
struct Bar {
    Bar() { std::cout << "Bar\n"; }
    ~Bar() { std::cout << "~Bar\n"; }
    static Bar &instance();
};

They are identical except for the implementations of their static instance member functions:

thread_local Foo t_foo;
Foo &Foo::instance() { return t_foo; }

Bar &Bar::instance() { static Bar s_bar; return s_bar; }

Bar is a Meyers singleton, a block-scope object with static storage duration.

Foo's instance is a namespace-scope object with thread-local storage duration.

Now the main function:

int main() {
    Bar::instance();
    Foo::instance();
}

Here's the output from GCC 8.1.0 and Clang 5.0.0:

Bar
Foo
~Foo
~Bar

Try it live: https://coliru.stacked-crooked.com/a/f83a9ec588aed921

I had expected that Foo would be constructed first, because it is at namespace scope. I suppose the implementation is permitted to defer the initialization until the first odr-use of the object. I didn't know it could be deferred until after the initialization of a block-scope static, but I can live with that.

Now I reverse the order of the function calls in main:

int main() {
    Foo::instance();
    Bar::instance();
}

And here's the output:

Foo
Bar
~Foo
~Bar

Now I've moved the first odr-use of the Foo instance to before the first call to Bar::instance, and the order of initialization is as I expected.

But I thought the objects should be destroyed in the reverse order of their initialization, which does not appear to be happening. What am I missing?

In relation to the initialization and destruction of objects of static and thread-local storage duration, cppreference and the standard say things like "when the program starts", "when the thread starts", "when the program ends", and "when the thread ends", but how do these concepts relate to each other in the context of the main thread? Or to be more precise, the first thread and the last thread?

In my "real" problem, Foo (the thread-local) is used by the logger, and the destructor of the base class of Bar uses the logger, so it's a static destruction order fiasco. Other threads are spawned, but Bar (the Meyers singleton) is constructed and destroyed in the main thread. If I could understand the sequencing rules, then I could try to solve the "real" problem without resorting to just trying things at random.

like image 670
Oktalist Avatar asked Jan 01 '19 23:01

Oktalist


People also ask

Can ThreadLocal be static?

ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Which objects have automatic storage duration?

A local variable declared at block scope normally has an automatic storage duration. Local variables are stored in a run-time stack.

Are static variables ThreadLocal?

static makes the ThreadLocal variable available across multiple classes for the respective thread only. it's a kind of Global variable decaration of the respective thread local variables across multiple classes.

What is static storage duration?

The static storage duration means that the variable will last for the entire execution time of the program. This means that a static variable will stay valid in the memory till the program is running.


1 Answers

The standard guarantees that destruction of Foo(the thread local storage) is before Bar(the static storage):

[basic.start.term]/2

The completions of the destructors for all initialized objects with thread storage duration within that thread strongly happen before the initiation of the destructors of any object with static storage duration.

However, there is no guarantee about the construction order. The standard only says that the thread local should be constructed before its first odr-use:

[basic.stc.thread]/2

A variable with thread storage duration shall be initialized before its first odr-use

like image 174
llllllllll Avatar answered Oct 23 '22 04:10

llllllllll