主线程中块作用域静态与命名空间作用域thread_local的初始化和销毁​​顺序

时间:2019-01-01 23:52:19

标签: c++ multithreading static c++14 static-order-fiasco

我正在尝试了解在主线程上下文中具有静态存储持续时间和线程本地存储持续时间的命名空间范围和块范围对象的初始化和销毁​​的排序规则。考虑以下两个类:

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和标准说诸如“程序何时启动”,“线程何时启动”,“程序何时终止”以及“线程何时终止”,但是这些概念在主线程的上下文中如何相互关联?或者更确切地说,第一个线程和最后一个线程?

在我的“实际”问题中,记录器使用Foo(线程局部),而Bar基类的析构函数使用记录器,因此这是静态销毁顺序惨败。会生成其他线程,但是Bar(Meyers单例)是在主线程中构造和销毁的。如果我能理解排序规则,那么我可以尝试解决“实际”问题,而不必依靠随机尝试。

1 个答案:

答案 0 :(得分:5)

该标准保证Foo(线程本地存储)的销毁在Bar(静态存储)之前:

[basic.start.term]/2

  

该线程内所有具有线程存储持续时间的初始化对象的析构函数的完成强烈地发生在具有静态存储持续时间的任何对象的析构函数的初始化之前。

但是,不能保证施工顺序。该标准只说应该在第一次使用odr之前构造线程本地变量:

[basic.stc.thread]/2

  

具有线程存储持续时间的变量应在其第一次使用odr之前初始化