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

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

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

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();
};

除了它们的静态实例成员函数的实现之外,它们是相同的:

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

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

Bar 是一个 Meyers 单例,一个具有静态存储持续时间的 block 范围对象。

Foo 的实例是具有线程本地存储持续时间的 namespace 范围对象。

现在是 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 使用。我不知道它可以推迟到 block 作用域静态初始化之后,但我可以接受。

现在我颠倒了 main 中函数调用的顺序:

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

这是输出:

Foo
Bar
~Foo
~Bar

现在我已经将 Foo 实例的第一次 odr-use 移动到第一次调用 Bar::instance 之前,并且初始化顺序与我一样预期。

但我认为对象应该按照与初始化相反的顺序销毁,但这似乎并没有发生。我错过了什么?

关于静态和线程本地存储持续时间的对象的初始化和销毁​​,cppreference 和标准说“程序启动时”,“线程启动时”,“程序结束时”,以及“当线程结束时”,但是这些概念在主线程的上下文中如何相互关联?或者更准确地说,第一个线程和最后一个线程?

在我的“真实”问题中,Foo(线程本地)被记录器使用,Bar 的基类的析构函数使用记录器,所以这是静态销毁命令的惨败。生成了其他线程,但在主线程中构造和销毁了 Bar(Meyers 单例)。如果我能理解排序规则,那么我就可以尝试解决“真正的”问题,而不必求助于随机尝试。

最佳答案

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

[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.

但是,无法保证施工顺序。该标准只说线程本地应该在它的第一次 odr-use 之前构造:

[basic.stc.thread]/2

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

关于c++ - 主线程中 block 作用域静态与命名空间作用域 thread_local 的初始化和销毁​​顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53999850/

相关文章:

c++ - 为什么 GCC 减去 1 并比较 <= 2? cmp 在汇编中使用 2 的幂会更快吗?

java - 应如何处理 CountDownLatch.wait 的 InterruptedException

java - 安卓 : is it safe to create a View and a Handler in a worker thread

c# - 在什么情况下我不应该在类中使用静态成员?

javascript - Koa.js - 提供静态文件和 REST API

c++如何在派生类中实现常量静态成员

c++ - 对 boost 库的依赖没有完整路径

c++ - 处理许多自定义异常的最佳方法是什么

python - 如何启动 IPython 内核并使用 ZMQ 套接字进行连接?

java - 线程饥饿