访问全局或静态的分离线程 objects
Detached threads accessing global or static objects
以下文本摘自 The C++ Standard Library: A Tutorial and Reference, 2nd Edition 一书的第 18.2.1† 节:
Note, however, that the lifetime problem also applies to global and static objects, because when the program exits, the detached thread might still run, which means that it might access global or static objects that are already destroyed or under destruction. Unfortunately, this would result in undefined behavior.
据我了解,所有分离的线程都将在 main()
结束时终止。
因此,我怀疑这种行为的原因是全局和静态 objects 的实际销毁顺序是 未指定 相对于分离线程'终止,即它可能发生在分离线程终止之前、期间或之后。
如能进一步说明此事,我们将不胜感激。
†更具体地说:在标题注意分离线程.[=35 下的小节中=]
所有静态初始化或延迟初始化的东西(例如,进入其包含块的块作用域静态变量)在正常程序终止期间都会被取消初始化 - 通过 main()
return调用或调用 exit()
。
问题不在于线程终止的顺序,而是根本没有努力阻止它们。相反,当进程终止时,(可能仍然是 运行ning)线程的收获被委托给操作系统来解决。
实际上,实现强制终止线程(分离的或其他方式)真的很困难。撇开其他不谈,这是导致不可预测行为的秘诀,因为这些线程几乎总是被同步对象或系统调用阻塞,并持有资源(你好,死锁!)。另一方面,Posix-Threads 不提供 API 来执行此操作。线程需要 return 从它们的线程函数中退出也就不足为奇了。
在 main()
returning 和进程终止之间有一段有限的时间,其中 运行-time 执行静态去初始化(与初始化)以及使用 atexit()
注册的任何内容,在此期间任何现存的线程仍然可以 运行ning。在大型程序中,这个时间可能很重要。
如果这些线程中的任何一个碰巧访问了一个静态初始化的对象,这当然是未定义的行为。
我最近花了相当多的时间来追踪一个大型 iOS 应用程序中的一系列崩溃,其中包含很多 C++。
崩溃的代码看起来很像这样,崩溃在 std::set<T>::find(const T&)
的深处
bool checkWord(const std::string &w)
{
static std::set<std::string> tags{"foo", "bar"};
return (tags.find(w) != tags.end());
}
与此同时,在主线程上,调用了 exit()
堆栈中的几个函数。
iOS 和 macOS 应用程序使用 Grand Central Dispatch/libdispatch 进行大量多线程处理,事实证明,不仅线程在 main()
退出后仍然 运行ning,但作业也在后台调度队列中执行。
我怀疑在许多其他系统上也会出现类似情况。
除了避免块作用域静态支持不需要初始化的数据之外,我没有找到解决问题的非常好的解决方案。
以下文本摘自 The C++ Standard Library: A Tutorial and Reference, 2nd Edition 一书的第 18.2.1† 节:
Note, however, that the lifetime problem also applies to global and static objects, because when the program exits, the detached thread might still run, which means that it might access global or static objects that are already destroyed or under destruction. Unfortunately, this would result in undefined behavior.
据我了解,所有分离的线程都将在 main()
结束时终止。
因此,我怀疑这种行为的原因是全局和静态 objects 的实际销毁顺序是 未指定 相对于分离线程'终止,即它可能发生在分离线程终止之前、期间或之后。
如能进一步说明此事,我们将不胜感激。
†更具体地说:在标题注意分离线程.[=35 下的小节中=]
所有静态初始化或延迟初始化的东西(例如,进入其包含块的块作用域静态变量)在正常程序终止期间都会被取消初始化 - 通过 main()
return调用或调用 exit()
。
问题不在于线程终止的顺序,而是根本没有努力阻止它们。相反,当进程终止时,(可能仍然是 运行ning)线程的收获被委托给操作系统来解决。
实际上,实现强制终止线程(分离的或其他方式)真的很困难。撇开其他不谈,这是导致不可预测行为的秘诀,因为这些线程几乎总是被同步对象或系统调用阻塞,并持有资源(你好,死锁!)。另一方面,Posix-Threads 不提供 API 来执行此操作。线程需要 return 从它们的线程函数中退出也就不足为奇了。
在 main()
returning 和进程终止之间有一段有限的时间,其中 运行-time 执行静态去初始化(与初始化)以及使用 atexit()
注册的任何内容,在此期间任何现存的线程仍然可以 运行ning。在大型程序中,这个时间可能很重要。
如果这些线程中的任何一个碰巧访问了一个静态初始化的对象,这当然是未定义的行为。
我最近花了相当多的时间来追踪一个大型 iOS 应用程序中的一系列崩溃,其中包含很多 C++。
崩溃的代码看起来很像这样,崩溃在 std::set<T>::find(const T&)
bool checkWord(const std::string &w)
{
static std::set<std::string> tags{"foo", "bar"};
return (tags.find(w) != tags.end());
}
与此同时,在主线程上,调用了 exit()
堆栈中的几个函数。
iOS 和 macOS 应用程序使用 Grand Central Dispatch/libdispatch 进行大量多线程处理,事实证明,不仅线程在 main()
退出后仍然 运行ning,但作业也在后台调度队列中执行。
我怀疑在许多其他系统上也会出现类似情况。
除了避免块作用域静态支持不需要初始化的数据之外,我没有找到解决问题的非常好的解决方案。