我有一个多线程的Windows服务器,我正在研究并发现在我通过control-c关闭程序时出现一组标准后,它会崩溃。如果我的服务器从客户端接收数据包然后我使用control-c,它会崩溃。如果我启动我的服务器,让它等待任何时间段的数据包,然后使用control-c,它会正常退出。
但有什么奇怪之处在于,我的所有主题都报告他们即使在程序确实抛出异常时也会以状态0退出(除非这是正常的)。
First-chance exception at 0x75A16DA7 (kernel32.dll) in server.exe: 0x40010005: Control-C.
HEAP[server.exe]: HEAP: Free Heap block 96a818 modified at 96a908 after it was freed
server.exe has triggered a breakpoint.
The thread 0xc34 has exited with code 0 (0x0).
The thread 0x1c64 has exited with code 0 (0x0).
The thread 0xdbc has exited with code 0 (0x0).
The thread 0x117c has exited with code 0 (0x0).
The thread 0x1444 has exited with code 0 (0x0).
The thread 0x1d60 has exited with code 0 (0x0).
The thread 0x798 has exited with code 0 (0x0).
The thread 0x700 has exited with code 0 (0x0).
The thread 0x1bbc has exited with code 0 (0x0).
The thread 0x1b74 has exited with code 0 (0x0).
The program '[7528] server.exe' has exited with code 0 (0x0).
代码中似乎是导致此问题的部分:
void handleSignal(int sig) {
std::unique_lock<std::mutex> lock(signalMutex); // <-- comment out and it doesn't crash
signaled = true;
_receivedSignal = sig;
signalHandlerCondition.notify_one(); // <-- comment out and it doesn't crash
}
互斥和条件变量都是全局变量:
std::mutex signalMutex;
std::condition_variable signalHandlerCondition;
我有一个专门的信号处理线程,试图在该事件通知时正常关闭服务器。
void run() {
while (gContinueRunning && _continueRunning) {
std::unique_lock<std::mutex> lock(signalMutex);
signalHandlerCondition.wait(lock);
if (signaled) {
gContinueRunning = false;
signaled = false;
Server::stop();
}
}
}
当然,当我注释掉有问题的线路时,程序根本不会响应信号。我可以有一个wait_for,这样我就不必通知信号处理循环它有一个新信号,但我不认为这是最好的方法。
我确实从MSDN上读到了有关信号的内容:
当发生CTRL + C中断时,Win32操作系统会生成一个新的 线程专门处理该中断。
因为信号处理程序例程通常在中断时异步调用 发生时,您的信号处理函数可以在运行时操作时获得控制权 不完整且处于未知状态。
老实说,我不确定这种情况是否适用。如果确实如此,那么当调用信号处理程序时,这是否意味着我的互斥锁可能存在或不存在?
那么,接近信号的最佳方式是什么?我遇到了什么问题?
编辑:只是为了澄清一些事情:
void start() {
_receivedSignal = 0;
_continueRunning = true;
// start thread
std::thread signalHandlerThread(run);
_signalHandlerThread = std::move(signalHandlerThread);
// register signals
signal(SIGABRT, SignalHandler::handleSignal);
signal(SIGTERM, SignalHandler::handleSignal);
signal(SIGINT, SignalHandler::handleSignal);
}
即使删除了互斥锁,看起来程序也会进一步发展 - 尽管只有在完成后才能完成。
msvcr110d.dll!operator delete(void * pUserData) Line 52 C++
server.exe!std::_Ref_count<User>::_Destroy() Line 161 C++
server.exe!std::_Ref_count_base::_Decref() Line 120 C++
server.exe!std::_Ptr_base<User>::_Decref() Line 347 C++
server.exe!std::shared_ptr<User>::~shared_ptr<User>() Line 624 C++
server.exe!std::pair<unsigned int const ,std::shared_ptr<User> >::~pair<unsigned int const ,std::shared_ptr<User> >() C++
server.exe!std::pair<unsigned int const ,std::shared_ptr<User> >::`scalar deleting destructor'(unsigned int) C++
server.exe!std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> >::destroy<std::pair<unsigned int const ,std::shared_ptr<User> > >(std::pair<unsigned int const ,std::shared_ptr<User> > * _Ptr) Line 624 C++
server.exe!std::allocator_traits<std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> > >::destroy<std::pair<unsigned int const ,std::shared_ptr<User> > >(std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> > & _Al, std::pair<unsigned int const ,std::shared_ptr<User> > * _Ptr) Line 758 C++
server.exe!std::_Wrap_alloc<std::allocator<std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> > >::destroy<std::pair<unsigned int const ,std::shared_ptr<User> > >(std::pair<unsigned int const ,std::shared_ptr<User> > * _Ptr) Line 909 C++
server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::_Erase(std::_Tree_node<std::pair<unsigned int const ,std::shared_ptr<User> >,void *> * _Rootnode) Line 2069 C++
server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::clear() Line 1538 C++
server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::erase(std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<unsigned int const ,std::shared_ptr<User> > > > > _First, std::_Tree_const_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<unsigned int const ,std::shared_ptr<User> > > > > _Last) Line 1512 C++
server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::_Tidy() Line 2216 C++
server.exe!std::_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >::~_Tree<std::_Tmap_traits<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > >,0> >() Line 1190 C++
server.exe!std::map<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > > >::~map<unsigned int,std::shared_ptr<User>,std::less<unsigned int>,std::allocator<std::pair<unsigned int const ,std::shared_ptr<User> > > >() C++
server.exe!`dynamic atexit destructor for 'User::_usersListBySession''() C++
msvcr110d.dll!doexit(int code, int quick, int retcaller) Line 584 C
msvcr110d.dll!exit(int code) Line 394 C
server.exe!__tmainCRTStartup() Line 549 C
server.exe!mainCRTStartup() Line 377 C
看起来所有其他线程都消失了。我想我可能在其他地方犯了错误。
感谢您清除信号功能的安全性。
编辑2:看起来像一个不相关的共享指针给我带来麻烦!我很高兴看到一些好消息。
编辑3:一个完全不相关的问题导致了崩溃。现在,世界上的一切都很好。
答案 0 :(得分:2)
我怀疑这是因为您的调试器正在处理Ctrl-C事件。
此MSDN article有以下内容:
如果正在调试控制台进程并且尚未禁用CTRL + C信号,则系统会生成DBG_CONTROL_C异常。引发此异常只是为了调试器的好处,应用程序永远不应该使用异常处理程序来处理它。如果调试器处理异常,应用程序将不会注意到CTRL + C,但有一个例外:警报等待将终止。如果调试器在未处理的情况下传递异常,则将CTRL + C传递给控制台进程并将其视为信号,如前所述。
您可以将事件过滤器设置为“输出 - 未处理”,以允许您的应用程序处理此问题。我附上了一个如何在WinDbg中设置它的屏幕截图。 Visual Studio在“Win32 Exceptions”下列出了这个。
修改强> 此外,我应该补充说,尝试在事件处理程序中锁定互斥锁被认为是不好的做法。如果在调用信号处理程序时已经获取了互斥锁,则会导致死锁,因为应用程序无法恢复,直到信号处理程序完成并且信号处理程序无法完成,直到获取互斥锁为止。虽然这在您的用例中不太可能,但在spurious wake-up或2 CTRL-C背靠背期间的CTRL-C可能会导致死锁。