在condition_variable :: wait调用过程中中断程序SIGINT,随后又调用exit,导致程序冻结

问题描述

我不确定我是否很好地理解了这个问题,因此我编写了一个演示该问题的小示例程序:

#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(),应用程序也不会退出

这是我想发生的事情

  1. 主线程正在等待通知,因此被this->cv.wait(lock,[this] () {return this->ready;});
  2. 阻塞
  3. 接收到SIGINT,并且信号中断wait()调用,这导致信号处理程序被调用
  4. 信号处理程序调用{​​{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();
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...