问题描述
我不确定我是否很好地理解了这个问题,因此我编写了一个演示该问题的小示例程序:
#include <iostream>
#include <csignal>
#include <mutex>
#include <condition_variable>
#include <thread>
class Application {
std::mutex cvMutex;
std::condition_variable cv;
std::thread t2;
bool ready = false;
// I kNow I'm accessing this without a lock,please ignore that
bool shuttingDown = false;
public:
void mainThread() {
auto lock = std::unique_lock<std::mutex>(this->cvMutex);
while (!this->shuttingDown) {
if (!this->ready) {
std::cout << "Main thread waiting.\n" << std::flush;
this->cv.wait(lock,[this] () {return this->ready;});
}
// Do the thing
this->ready = false;
std::cout << "Main thread notification recieved.\n" << std::flush;
}
};
void notifyMainThread() {
std::cout << "Notifying main thread.\n" << std::flush;
this->cvMutex.lock();
this->ready = true;
this->cv.notify_all();
this->cvMutex.unlock();
std::cout << "Notified.\n" << std::flush;
};
void threadTwo() {
while(!this->shuttingDown) {
// Wait some seconds,then notify main thread
std::cout << "Thread two sleeping for some seconds.\n" << std::flush;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Thread two calling notifyMainThread().\n" << std::flush;
this->notifyMainThread();
}
std::cout << "Thread two exiting.\n" << std::flush;
};
void run() {
this->t2 = std::thread(&Application::threadTwo,this);
this->mainThread();
};
void shutdown() {
this->shuttingDown = true;
this->notifyMainThread();
std::cout << "Joining thread two.\n" << std::flush;
this->t2.join();
std::cout << "Thread two joined.\n" << std::flush;
// The following call causes the program to hang when triggered by a signal handler
exit(EXIT_SUCCESS);
}
};
auto app = Application();
int sigIntCount = 0;
int main(int argc,char *argv[])
{
std::signal(SIGINT,[](int signum) {
std::cout << "SIGINT recieved!\n" << std::flush;
sigIntCount++;
if (sigIntCount == 1) {
// First SIGINT recieved,attempt a clean shutdown
app.shutdown();
} else {
abort();
}
});
app.run();
return 0;
}
您可以在以下位置在线运行程序:https://onlinegdb.com/Bkjf-4RHP
上面的示例是一个简单的多线程应用程序,由两个线程组成。主线程等待条件变量,直到收到通知并且this->ready
已设置为true
。第二个线程只是简单地更新this->ready
并定期通知主线程。最后,应用程序在主线程上处理SIGINT ,在该线程中尝试执行干净关闭。
问题:
(通过Ctrl + C)触发SIGINT时,即使在exit()
中调用Application::shutdown()
,应用程序也不会退出。
这是我想发生的事情
- 主线程正在等待通知,因此被
this->cv.wait(lock,[this] () {return this->ready;});
阻塞
- 接收到SIGINT,并且信号中断
wait()
调用,这导致信号处理程序被调用。 - 信号处理程序调用{{1}},随后调用
Application::shutdown()
。对exit()
的调用会无限期挂起,因为它正在尝试清理某些问题,直到exit()
调用恢复为止(我不确定)。
我真的不确定最后一点,但这就是为什么我认为是这样:
- 当我在
wait()
中删除对exit()
的调用并让Application::shutdown()
返回时,程序将退出而没有问题。 - 当我用
main()
替换对exit()
的调用时,它对清理的作用较小,程序将退出而不会出现问题(因此表明由exit()进行的清理过程会导致冻结)。 - 如果在主线程未等待条件变量时发送了SIGINT,则程序将退出而不会出现问题。
以上只是我遇到的问题的一个示例。就我而言,我需要在abort()
中调用exit()
,并且需要从信号处理程序中调用shutdown()
。到目前为止,我的选择似乎是:
- 将所有信号处理移到专用线程中。这样做很麻烦,因为它将需要重写代码,以使我能够从与拥有
shutdown()
实例的线程不同的线程中调用Application::shutdown()
。我还需要一种将主线程从Application
调用中拉出的方法,可能是通过向谓词添加一些wait()
条件。 - 将呼叫
OR
替换为exit()
。这会起作用,但会导致堆栈未解卷(特别是abort()
实例)。
我还有其他选择吗?有没有办法在调用Application
期间正确地中断线程,并从中断处理程序中退出程序?
解决方法
[support.signal] / 3 评估是信号安全的,除非它包括以下内容之一:
(3.1)—调用任何标准库函数,但无锁无原子操作和函数除外 明确标识为信号安全。
...如果信号处理程序调用包含不安全信号的评估,则它具有未定义的行为。
您的程序显示未定义的行为。信号处理程序在安全操作方面受到很大限制。
,正如Igor所提到的,您实际上不能在信号处理程序中做太多事情。不过,您可以对无锁原子变量进行操作,因此可以修改代码以对其进行处理。
我已经添加了它,并做了一些其他更改,并对我在代码中建议的更改发表了评论:
#include <atomic>
#include <condition_variable>
#include <csignal>
#include <iostream>
#include <mutex>
#include <thread>
// Make sure the atomic type we'll operate on is lock-free.
static_assert(std::atomic<bool>::is_always_lock_free);
class Application {
std::mutex cvMutex;
std::condition_variable cv;
std::thread t2;
bool ready = false;
static std::atomic<bool> shuttingDown; // made it atomic
public:
void mainThread() {
std::unique_lock<std::mutex> lock(cvMutex);
while(!shuttingDown) {
// There is no need to check if(!ready) here since
// the condition in the cv.wait() lambda will be checked
// before it is going to wait,like this:
//
// while(!ready) cv.wait(lock);
std::cout << "Main thread waiting." << std::endl; // endl = newline + flush
cv.wait(lock,[this] { return ready; });
std::cout << "Main thread notification recieved." << std::endl;
// Do the thing
ready = false;
}
}
void notifyMainThread() {
{ // lock scope - don't do manual lock() / unlock()-ing
std::lock_guard<std::mutex> lock(cvMutex);
std::cout << "Notifying main thread." << std::endl;
ready = true;
}
cv.notify_all(); // no need to hold lock when notifying
}
void threadTwo() {
while(!shuttingDown) {
// Wait some seconds,then notify main thread
std::cout << "Thread two sleeping for some seconds." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Thread two calling notifyMainThread()." << std::endl;
notifyMainThread();
}
std::cout << "Time to quit..." << std::endl;
notifyMainThread();
std::cout << "Thread two exiting." << std::endl;
}
void run() {
// Installing the signal handler as part of starting the application.
std::signal(SIGINT,[](int /* signum */) {
// if we have received the signal before,abort.
if(shuttingDown) abort();
// First SIGINT recieved,attempt a clean shutdown
shutdown();
});
t2 = std::thread(&Application::threadTwo,this);
mainThread();
// move join()ing out of the signal handler
std::cout << "Joining thread two." << std::endl;
t2.join();
std::cout << "Thread two joined." << std::endl;
}
// This is made static. All instances of Application
// will likely need to shutdown.
static void shutdown() { shuttingDown = true; }
};
std::atomic<bool> Application::shuttingDown = false;
int main() {
auto app = Application();
app.run();
}