如果在使用VS2012 RC时main()退出后调用,则std :: thread :: join()会挂起

Fra*_*ser 23 c++ visual-c++ c++11 stdthread visual-c++-2012

如果在Ubuntu 12.04上使用Clang 3.2或GCC 4.7进行编译,则以下示例成功运行(即不挂起),但如果我使用VS11 Beta或VS2012 RC进行编译,则会挂起.

#include <iostream>
#include <string>
#include <thread>
#include "boost/thread/thread.hpp"

void SleepFor(int ms) {
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

template<typename T>
class ThreadTest {
 public:
  ThreadTest() : thread_([] { SleepFor(10); }) {}
  ~ThreadTest() {
    std::cout << "About to join\t" << id() << '\n';
    thread_.join();
    std::cout << "Joined\t\t" << id() << '\n';
  }
 private:
  std::string id() const { return typeid(decltype(thread_)).name(); }
  T thread_;
};

int main() {
  static ThreadTest<std::thread> std_test;
  static ThreadTest<boost::thread> boost_test;
//  SleepFor(100);
}
Run Code Online (Sandbox Code Playgroud)

问题似乎是,std::thread::join()如果在main退出后调用它,则永远不会返回.它在cthread.c WaitForSingleObject_Thrd_join定义时被阻止.

SleepFor(100);在最后取消注释main允许程序正确退出,就像制作std_test非静态程序一样.使用boost::thread也避免了这个问题.

所以我想知道我是否在这里调用未定义的行为(似乎不太可能),或者我是否应该提交针对VS2012的错误?

CWo*_*ods 25

使用VS2012 RTM 在他的连接错误(https://connect.microsoft.com/VisualStudio/feedback/details/747145)中跟踪Fraser的示例代码似乎显示了一个相当简单的死锁情况.这可能不具体std::thread- 可能_beginthreadex遭受同样的命运.

我在调试器中看到的内容如下:

在主线程上,main()函数已经完成,进程清理代码已经获得了一个_EXIT_LOCK1称为析构函数的临界区ThreadTest,并且正在等待(无限期地)在第二个线程上退出(通过调用join()).

第二个线程的匿名函数已完成,并在线程清理代码中等待获取_EXIT_LOCK1临界区.不幸的是,由于事物的时间安排(第二个线程的匿名函数的生命周期超过函数的生命周期main()),主线程已经拥有该关键部分.

僵局.

延长main()第二个线程_EXIT_LOCK1在主线程之前可以获取的生命周期的任何东西都可以避免死锁情况.这就是为什么取消注释睡眠会main()导致干净关闭.

或者,如果从ThreadTest局部变量中删除static关键字,则析构函数调用将移动到main()函数的末尾(而不是在进程清理代码中),然后阻塞直到第二个线程退出 - 避免死锁情况.

或者您可以ThreadTest为该调用添加一个函数join()并在结束时调用该函数main()- 再次避免死锁情况.


小智 6

我意识到这是一个关于VS2012的老问题,但该错误仍然存​​在于VS2013中.对于那些坚持VS2013的人,可能是由于微软拒绝为VS2015提供升级价格,我提供了以下分析和解决方法.

问题是,使用的互斥锁(at_thread_exit_mutex)_Cnd_do_broadcast_at_thread_exit()尚未初始化,或者已经被破坏,具体取决于具体情况.在前一种情况下,_Cnd_do_broadcast_at_thread_exit()尝试在关闭期间初始化互斥锁,从而导致死锁.在后一种情况下,互斥体已经通过atexit堆栈销毁,程序将在出路时崩溃.

我找到的解决方案是_Cnd_do_broadcast_at_thread_exit()在程序启动期间尽早显式调用(幸运的是,公开声明).这具有在其他人尝试访问互斥锁之前创建互斥锁的效果,以及确保互斥锁继续存在直到最后一刻.

因此,要解决此问题,请在源模块的底部插入以下代码,例如main()下面的某个位置.

#pragma warning(disable:4073) // initializers put in library initialization area
#pragma init_seg(lib)

#if _MSC_VER < 1900
struct VS2013_threading_fix
{
    VS2013_threading_fix()
    {
        _Cnd_do_broadcast_at_thread_exit();
    }
} threading_fix;
#endif
Run Code Online (Sandbox Code Playgroud)