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)