简体   繁体   English

通过安装信号处理程序来关闭多线程应用程序

[英]Shutting down a multithreaded application by installing a signal handler

In the following code, I create a toy class that has a thread which writes to a queue while the other thread reads from that queue and prints it to stdout . 在下面的代码中,我创建了一个玩具类,该类具有一个写入队列的线程,而另一个线程从该队列读取并将其打印到stdout Now, in order to cleanly shutdown the system, I setup a handler for SIGINT . 现在,为了彻底关闭系统,我为SIGINT设置了一个处理程序。 I am expecting the signal handler to set up the std::atomic<bool> variable stopFlag , which will lead threadB to push a poison pill (sentinel) on to the queue encountering which threadA will halt. 我期望信号处理程序设置std::atomic<bool>变量stopFlag ,这将导致threadB将毒药(前哨)推到队列中,遇到哪个threadA将停止的情况。

class TestClass
{
public:

    TestClass();
    ~TestClass();
    void shutDown();

    TestClass(const TestClass&) = delete;
    TestClass& operator=(const TestClass&) = delete;


private:
    void init();
    void postResults();
    std::string getResult();
    void processResults();

    std::atomic<bool> stopFlag;

    std::mutex outQueueMutex;
    std::condition_variable outQueueConditionVariable;
    std::queue<std::string> outQueue;

    std::unique_ptr<std::thread> threadA;
    std::unique_ptr<std::thread> threadB;
};

void TestClass::init()
{
    threadA = std::make_unique<std::thread>(&TestClass::processResults, std::ref(*this));
    threadB = std::make_unique<std::thread>(&TestClass::postResults, std::ref(*this));
}

TestClass::TestClass():
    stopFlag(false)
{
    init();
}

TestClass::~TestClass()
{
    threadB->join();
}

void TestClass::postResults()
{
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        std::string name = "ABCDEF";
        {
            std::unique_lock<std::mutex> lock(outQueueMutex);
            outQueue.push(name);
            outQueueConditionVariable.notify_one();
        }
        if(stopFlag)
        {
            /*For shutting down output thread*/
            auto poisonPill = std::string();
            {
                std::unique_lock<std::mutex> lock(outQueueMutex);
                outQueue.push(poisonPill);
                outQueueConditionVariable.notify_one();
            }
            threadA->join();
            break;
        }
    }
}

void TestClass::shutDown()
{
    stopFlag = true;
}

std::string TestClass::getResult()
{
    std::string result;
    {
        std::unique_lock<std::mutex> lock(outQueueMutex);
        while(outQueue.empty())
        {
            outQueueConditionVariable.wait(lock);
        }
        result= outQueue.front();
        outQueue.pop();
    }
    return result;
}

void TestClass::processResults()
{
    while(true)
    {
        const auto result = getResult();

        if(result.empty())
        {
            break;
        }

        std::cout << result << std::endl;

    }
}

static void sigIntHandler(std::shared_ptr<TestClass> t, int)
{
    t->shutDown();
}
static std::function<void(int)> handler;

int main()
{
    auto testClass = std::make_shared<TestClass>();
    handler = std::bind(sigIntHandler, testClass, std::placeholders::_1);
    std::signal(SIGINT, [](int n){ handler(n);});
    return 0;
}

I compiled this using gcc 5.2 using the -std=c++14 flag. 我使用-std = c ++ 14标志使用gcc 5.2对此进行了编译。 On hitting Ctrl-C on my CentOS 7 machine, I get the following error, 在CentOS 7计算机上按Ctrl-C时,出现以下错误,

terminate called after throwing an instance of 'std::system_error'
  what():  Invalid argument
Aborted (core dumped)

Please help me understand what is going on. 请帮助我了解发生了什么。

On your platform this signal handler is invoked when a real SIGINT signal comes. 在您的平台上,当出现实际SIGINT信号时,将调用此信号处理程序。 The list of functions that can be invoked inside of this signal handler is rather limited and calling anything else leads to an undefined behavior. 可以在此信号处理程序内部调用的函数列表相当有限,调用任何其他函数都会导致未定义的行为。

What happens is that your main function exits immediately destroying global handler object and then testClass . 发生的情况是您的main功能立即退出,先销毁全局handler对象,然后销毁testClass Then the main thread gets blocked in TestClass::~TestClass . 然后主线程在TestClass::~TestClass被阻塞。 The signal handler ends up accessing already destroyed objects, which leads to the undefined behaviour. 信号处理程序最终访问已毁坏的对象,这导致不确定的行为。

The root cause is undefined object ownership due to shared pointers - you do not know what and when ends up destroying your objects. 根本原因是由于共享指针导致的对象所有权不确定-您不知道什么以及何时破坏对象。


A more general approach is to use another thread to handle all signals and block signals in all other threads. 一种更通用的方法是使用另一个线程来处理所有信号,并阻塞所有其他线程中的信号。 That signal handling thread then can call any functions upon receiving a signal. 然后,该信号处理线程可以在接收到信号后调用任何函数。

You also do not need the smart pointers and function wrappers here at all. 您也完全不需要智能指针和函数包装器。

Example: 例:

class TestClass
{
public:
    TestClass();
    ~TestClass();
    void shutDown();

    TestClass(const TestClass&) = delete;
    TestClass& operator=(const TestClass&) = delete;

private:
    void postResults();
    std::string getResult();
    void processResults();


    std::mutex outQueueMutex;
    std::condition_variable outQueueConditionVariable;
    std::queue<std::string> outQueue;
    bool stop = false;

    std::thread threadA;
    std::thread threadB;
};

TestClass::TestClass()
    : threadA(std::thread(&TestClass::processResults, this))
    , threadB(std::thread(&TestClass::postResults, this))
{}

TestClass::~TestClass() {
    threadA.join();
    threadB.join();
}

void TestClass::postResults() {
    while(true) {
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        std::string name = "ABCDEF";
        {
            std::unique_lock<std::mutex> lock(outQueueMutex);
            if(stop)
                return;
            outQueue.push(name);
            outQueueConditionVariable.notify_one();
        }
    }
}

void TestClass::shutDown() {
    std::unique_lock<std::mutex> lock(outQueueMutex);
    stop = true;
    outQueueConditionVariable.notify_one();
}

std::string TestClass::getResult() {
    std::string result;
    {
        std::unique_lock<std::mutex> lock(outQueueMutex);
        while(!stop && outQueue.empty())
            outQueueConditionVariable.wait(lock);
        if(stop)
            return result;
        result= outQueue.front();
        outQueue.pop();
    }
    return result;
}

void TestClass::processResults()
{
    while(true) {
        const auto result = getResult();
        if(result.empty())
            break;
        std::cout << result << std::endl;
    }
}

int main() {
    // Block signals in all threads.
    sigset_t sigset;
    sigfillset(&sigset);
    ::pthread_sigmask(SIG_BLOCK, &sigset, nullptr);

    TestClass testClass;

    std::thread signal_thread([&testClass]() {
        // Unblock signals in this thread only.
        sigset_t sigset;
        sigfillset(&sigset);
        int signo = ::sigwaitinfo(&sigset, nullptr);
        if(-1 == signo)
            std::abort();

        std::cout << "Received signal " << signo << '\n';
        testClass.shutDown();
    });

    signal_thread.join();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM