Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/143.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 在条件变量::wait()调用期间中断程序(SIGINT)并随后调用exit(),会导致程序冻结_C++_Multithreading_Deadlock_Condition Variable_Resource Cleanup - Fatal编程技术网

C++ 在条件变量::wait()调用期间中断程序(SIGINT)并随后调用exit(),会导致程序冻结

C++ 在条件变量::wait()调用期间中断程序(SIGINT)并随后调用exit(),会导致程序冻结,c++,multithreading,deadlock,condition-variable,resource-cleanup,C++,Multithreading,Deadlock,Condition Variable,Resource Cleanup,我不确定我是否非常理解这个问题,因此我编写了一个小示例程序来演示它: #include <iostream> #include <csignal> #include <mutex> #include <condition_variable> #include <thread> class Application { std::mutex cvMutex; std::condition_variable cv;

我不确定我是否非常理解这个问题,因此我编写了一个小示例程序来演示它:

#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;
}
您可以在此处联机运行该程序:

上面的示例是一个由两个线程组成的简单多线程应用程序。主线程等待条件变量,直到收到通知并且此->就绪设置为true。第二个线程只是定期更新这个->就绪并通知主线程。最后,应用程序在主线程上处理SIGINT,并尝试执行干净的关闭

问题是:

当通过Ctrl+C触发SIGINT时,应用程序不会退出,尽管在application::shutdown中调用了exit

这就是我认为正在发生的事情:

主线程正在等待通知,因此它被this->cv.waitlock、[this]{returnthis->ready;}阻止; 接收到SIGINT,等待调用被信号中断,从而导致调用信号处理程序。 信号处理程序调用Application::shutdown,后者随后调用exit。对退出的调用无限期挂起,因为它正在尝试一些在等待调用恢复之前无法实现的清理。对此我不确定。 我真的不确定最后一点,但这就是为什么我认为是这样的:

当我在Application::shutdown中删除对exit的调用并让main返回时,程序将毫无问题地退出。 当我用abort替换对exit的调用时(这在清理方面做得更少),程序会毫无问题地退出,因此这表明exit执行的清理过程会导致冻结。 如果在主线程未等待条件变量时发送SIGINT,则程序将无问题退出。 以上只是我遇到的问题的一个例子。在我的例子中,我需要在shutdown中调用exit,并且shutdown需要从信号处理程序中调用。到目前为止,我的选择似乎是:

将所有信号传递移动到专用线程中。这将是一件痛苦的事情,因为它需要重写代码,使我能够从另一个线程调用Application::shutdown,而不是从拥有该实例的线程调用Application::shutdown。我还需要一种将主线程从等待调用中拉出的方法,可能是向谓词添加一些OR条件。 将退出调用替换为中止调用。这会起作用,但会导致堆栈没有展开,特别是应用程序实例。 我还有其他选择吗?在调用std::condition_variable::wait期间,是否有任何方法可以正确中断线程,并从中断处理程序中退出程序

[support.signal]/3评估是信号安全的,除非它包括以下内容之一:

3.1-对任何标准库函数的调用,普通无锁原子操作和函数除外 明确标识为信号安全。

如果信号处理程序调用包含非信号安全的计算,则该调用具有未定义的行为

您的程序显示未定义的行为。信号处理器的安全性非常有限

[support.signal]/3评估是信号安全的,除非它包括以下内容之一:

3.1-对任何标准库函数的调用,普通无锁原子操作和函数除外 明确标识为信号安全。

如果信号处理程序调用包含非信号安全的计算,则该调用具有未定义的行为


您的程序显示未定义的行为。信号处理程序的安全性非常有限。

正如Igor所提到的,在信号处理程序中不能做很多事情。不过,您可以对无锁原子变量进行操作,因此可以修改代码以处理该变量

我已经添加了这一点,并做了一些其他更改,并对我建议的代码更改发表了评论:

包括 包括 包括 包括 包括 包括 //确保我们要操作的原子类型是无锁的。 静态资产std::原子::总是没有锁; 班级申请{ std::mutex-cvMutex; std::条件变量cv; std::螺纹t2; bool ready=false; 静态std::原子关闭;//使其原子化 公众: 无效主线程{ std::唯一的_lock-lockcvMutex; 停下来{ //没有必要检查!是否准备好了 //将检查cv.wait lambda中的条件 //在它等待之前,像这样: // //while!ready cv.waitlock;
正如Igor所提到的,在信号处理程序中不能做太多工作。不过,您可以对无锁原子变量进行操作,因此可以修改代码来处理它

我已经添加了这一点,并做了一些其他更改,并对我建议的代码更改发表了评论:

包括 包括 包括 包括 包括 包括 //确保我们要操作的原子类型是无锁的。 静态资产std::原子::总是没有锁; 班级申请{ std::mutex-cvMutex; std::条件变量cv; std::螺纹t2; bool ready=false; 静态std::原子关闭;//使其原子化 公众: 无效主线程{ std::唯一的_lock-lockcvMutex; 停下来{ //没有必要检查!是否准备好了 //将检查cv.wait lambda中的条件 //在它等待之前,像这样: // //while!ready cv.waitlock;
std::非常感谢您的回答,Ted。出于好奇,避免手动锁定调用以锁定和解锁的原因是什么?是为了避免在调用之间引发异常时可能出现的问题?还是有其他原因?@JohnO'brien不客气!是的,这是主要原因。第二个原因是代码的读者要ot会陷入困境。当使用手动资源管理读取代码时,我立即开始调查它是否会泄漏所述资源。在您的情况下,我必须检查notify_all是否可以抛出。当然,它不能,但仍然是::-不遗漏任何内容是一种好形式。即使在这样的短函数中使用RAII包装器,也可以使其安全且简单nd阅读代码的人不会把时间花在无关的事情上。谢谢你的回答,Ted。出于好奇,避免手动锁定调用以锁定和解锁的原因是什么?是为了避免调用之间抛出异常时可能出现的问题吗?还是有其他原因?@JohnO'brien不客气!是的,这是主要原因儿子。第二个原因是代码的读者不要被卡住。当使用手动资源管理阅读代码时,我立即开始调查它是否会泄漏所述资源。在您的情况下,我必须检查notify_all是否可以抛出。嗯,它不能,但仍然是。:-这是一种不遗漏任何内容的好形式。使用RAII包装器甚至可以n像这样的短函数使它安全、简单,人们阅读代码时不会花时间在不相关的东西上。