主线程中块作用域静态与命名空间作用域 thread_local 的初始化和销毁顺序
Order of initialization and destruction of block-scope static vs. namespace-scope thread_local in main thread
我试图了解在主线程上下文中具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的初始化和销毁的顺序规则。考虑这两个 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();
};
除了静态 instance
成员函数的实现外,它们完全相同:
thread_local Foo t_foo;
Foo &Foo::instance() { return t_foo; }
Bar &Bar::instance() { static Bar s_bar; return s_bar; }
Bar
是一个 Meyers 单例,一个具有静态存储持续时间的块范围对象。
Foo
的实例是具有线程本地存储持续时间的名称空间范围对象。
现在 main
函数:
int main() {
Bar::instance();
Foo::instance();
}
这是 GCC 8.1.0 和 Clang 5.0.0 的输出:
Bar
Foo
~Foo
~Bar
现场试玩:https://coliru.stacked-crooked.com/a/f83a9ec588aed921
我原以为 Foo
会先构造,因为它在命名空间范围内。我想允许实现将初始化推迟到对象的第一次 ODR 使用。我不知道它可以推迟到块作用域静态初始化之后,但我可以接受。
现在我颠倒了 main
中的函数调用顺序:
int main() {
Foo::instance();
Bar::instance();
}
这是输出:
Foo
Bar
~Foo
~Bar
现在我已经将 Foo
实例的第一次 ODR 使用移动到第一次调用 Bar::instance
之前,并且初始化顺序符合我的预期。
但我认为对象应该按照与初始化相反的顺序销毁,但这似乎并没有发生。我错过了什么?
关于静态和线程本地存储持续时间对象的初始化和销毁,cppreference 和标准说 "when the program starts"、"when the thread starts"、"when the program ends" 和 "when the thread ends",但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,第一个线程和最后一个线程?
在我的"real"问题中,记录器使用Foo
(线程本地),Bar
的基class的析构函数使用记录器,所以这是静态销毁命令的惨败。生成了其他线程,但 Bar
(Meyers 单例)在主线程中构造和销毁。如果我能理解排序规则,那么我就可以尝试解决 "real" 问题,而不必只是随机尝试。
标准保证Foo
(线程本地存储)的销毁在Bar
(静态存储)之前:
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.
但是,无法保证施工顺序。该标准只说线程本地应该在它的第一次 odr-use 之前构造:
A variable with thread storage duration shall be initialized before its first odr-use
我试图了解在主线程上下文中具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的初始化和销毁的顺序规则。考虑这两个 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();
};
除了静态 instance
成员函数的实现外,它们完全相同:
thread_local Foo t_foo;
Foo &Foo::instance() { return t_foo; }
Bar &Bar::instance() { static Bar s_bar; return s_bar; }
Bar
是一个 Meyers 单例,一个具有静态存储持续时间的块范围对象。
Foo
的实例是具有线程本地存储持续时间的名称空间范围对象。
现在 main
函数:
int main() {
Bar::instance();
Foo::instance();
}
这是 GCC 8.1.0 和 Clang 5.0.0 的输出:
Bar
Foo
~Foo
~Bar
现场试玩:https://coliru.stacked-crooked.com/a/f83a9ec588aed921
我原以为 Foo
会先构造,因为它在命名空间范围内。我想允许实现将初始化推迟到对象的第一次 ODR 使用。我不知道它可以推迟到块作用域静态初始化之后,但我可以接受。
现在我颠倒了 main
中的函数调用顺序:
int main() {
Foo::instance();
Bar::instance();
}
这是输出:
Foo
Bar
~Foo
~Bar
现在我已经将 Foo
实例的第一次 ODR 使用移动到第一次调用 Bar::instance
之前,并且初始化顺序符合我的预期。
但我认为对象应该按照与初始化相反的顺序销毁,但这似乎并没有发生。我错过了什么?
关于静态和线程本地存储持续时间对象的初始化和销毁,cppreference 和标准说 "when the program starts"、"when the thread starts"、"when the program ends" 和 "when the thread ends",但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,第一个线程和最后一个线程?
在我的"real"问题中,记录器使用Foo
(线程本地),Bar
的基class的析构函数使用记录器,所以这是静态销毁命令的惨败。生成了其他线程,但 Bar
(Meyers 单例)在主线程中构造和销毁。如果我能理解排序规则,那么我就可以尝试解决 "real" 问题,而不必只是随机尝试。
标准保证Foo
(线程本地存储)的销毁在Bar
(静态存储)之前:
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.
但是,无法保证施工顺序。该标准只说线程本地应该在它的第一次 odr-use 之前构造:
A variable with thread storage duration shall be initialized before its first odr-use