4

我有一个正在处理的多线程 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);
}

即使在删除互斥体之后,程序看起来也进展得更远了——尽管只是直到 main 完成。

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:一个完全不相关的问题导致了崩溃。不过,现在世界上一切都很好。

4

1 回答 1

2

我怀疑这是因为您的调试器正在处理 Ctrl-C 事件。

这篇MSDN 文章有以下要说的:

如果正在调试控制台进程并且没有禁用 CTRL+C 信号,则系统会生成 DBG_CONTROL_C 异常。引发此异常只是为了调试器的好处,应用程序永远不应使用异常处理程序来处理它。如果调试器处理异常,应用程序将不会注意到 CTRL+C,但有一个异常:警报等待将终止。如果调试器在未处理时传递异常,则 CTRL+C 将传递到控制台进程并作为信号处理,如前所述。

您可以将事件过滤器设置为“输出 - 未处理”以允许您的应用程序处理此问题。我附上了如何在 WinDbg 中进行设置的屏幕截图。Visual Studio 将此列在“Win32 异常”下。

WinDBG 事件过滤器

编辑: 另外,我应该补充一点,尝试在事件处理程序中锁定互斥锁被认为是不好的做法。如果在调用信号处理程序时已经获取了互斥锁,则会导致死锁,因为在信号处理程序完成之前应用程序无法恢复,并且在获取互斥体之前信号处理程序无法完成。虽然这在您的用例中不太可能发生,但虚假唤醒期间的 CTRL-C或 2 个 CTRL-C 背靠背可能会导致死锁。

于 2012-11-26T20:52:22.250 回答