我正在尝试使用 std::signal 干净地结束我的多线程程序,我做错了什么?

如何解决我正在尝试使用 std::signal 干净地结束我的多线程程序,我做错了什么?

我想做什么

我有很多东西必须在 linux 上同时运行,直到程序被告知通过 ctrl-C 停止(在这种情况下收到 SIGINT)或服务停止(在这种情况下收到 SIGTERM)

我想到了什么

对于需要同时完成的每一件事,我有一个类在构造函数中启动一个线程,其析构函数使线程停止并加入它。它看起来基本上是这样的:

#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <system_error>

class SomeClasstodoStuff
{
public:

    // Constructor
    SomeClasstodoStuff()
    {
        _thread = std::thread([this]() {
            while (true)
            {
                // Do some stuff
                ...

                // Before going on to the next iteration
                {
                    std::unique_lock<std::mutex> dataLock(_mutex);

                    // Wait for 2ms
                    if (!_shouldStopThreadExecution)
                    {
                        _conditionVariable.wait_for(dataLock,std::chrono::milliseconds(2));
                    }

                    // End the loop if requested
                    if (_shouldStopThreadExecution)
                    {
                        break;
                    }
                }
            }

            // Some cleanup
            ...
        });
    }

    // Destructor
    ~SomeClasstodoStuff()
    {
        if (_thread.joinable())
        {
            {
                std::lock_guard<std::mutex> dataLock(_mutex);
                _shouldStopThreadExecution = true;
            }

            _conditionVariable.notify_all();

            try
            {
                _thread.join();
            } catch (std::system_error const&) {}
        }
    }

private:
    mutable std::mutex _mutex;                  // Mutex to keep things thread-safe
    std::condition_variable _conditionVariable; // Condition variable used to wait
    std::thread _thread;                        // Thread in which to do the stuff
    bool _shouldStopThreadExecution = false;    // Whether to stop the thread's execution
};

然后我的 main() 看起来像这样:

#include <atomic>
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>

namespace  {

std::atomic<int> programReturnValue(-1);  // If positive or zero,the program must return with that value

}

static void signalHandler(int sig)
{
    std::cout << "Signal received (" << sig << "). This iteration of the main loop will be the last." << std::endl;
    programReturnValue.store(0);
}

int main()
{
    // Make sure the program stops cleanly when receiving SIGTERM or SIGINT
    {
        std::signal(SIGTERM,signalHandler);
        std::signal(SIGINT,signalHandler);
    }

    SomeClasstodoStuffA a;
    SomeClasstodoStuffB b;
    SomeClasstodoStuffC c;
    SomeClasstodoStuffD d;

    while (programReturnValue.load() < 0)
    {
        // Check that everything is alright
        if (someCondition)
        {
            programReturnValue.store(1);
        }

        // Wait for 100us to avoid taking all of the processor's resources
        std::this_thread::sleep_for(std::chrono::microseconds(100));
    }

    return programReturnValue.load();
}

(顺便说一句,如果有更简单的方法解决所有这些问题,我很想知道)

问题

当我按 ctrl+C 或结束服务时,程序打印出已收到信号 2 或 15(取决于我使用的),程序结束,这很好。 但是:

  1. 清理涉及向文件写入一些内容(在执行过程中成功写入内容),但似乎并不总是发生,这意味着清理并未始终完全执行,即一个问题
  2. 程序的返回码不是预期的 0,甚至不是 1,而是 130 或 143,具体取决于接收到的信号

为什么会这样,我做错了什么?

编辑:据我所知,130和143实际上是128+信号,即如果我不尝试处理信号,程序会返回什么

Edit2:我对正在发生的事情有了更好的了解,似乎只有一半的问题来自我的程序本身。

该程序实际上是由一个 bash 脚本运行的,然后它会打印其返回值,并可能根据情况重新启动它。向脚本发送 SIGINT 和 SIGTERM 也应该向程序发送 SIGTERM。

事实证明我很讨厌 bash。我写过这样的东西:

#!/bin/sh

trap "killall myProgram --quiet --wait" 2 15

/path/to/myProgram&
wait $!
RETURN_VALUE=$?
echo "Code exited with return code ${RETURN_VALUE}"

# Some other stuff
...
  • 在终端中运行脚本时按 ctrl-C 实际上会导致程序同时接收 SIGINT 和 SIGTERM
  • 我打印的返回码实际上是 wait+trap 的结果,而不是我的程序

我将重新编写脚本,但是两个信号都发送到我的程序这一事实是否可以成为清理有时失败的原因?如何?我该怎么办?

解决方法

我对你的信号处理有点困惑:

在我看来,您似乎只使用终止 System-signal 来设置返回值并中断 main 中的 while 循环;线程,或者更确切地说它们的包装器被终止,即仅在它们超出范围时才被破坏,这是在您的主范围结束时,当您已经返回时!无法再捕获抛出的异常(在您的析构函数中)。 因此,您的线程尚未结束,而您已经从主线程返回。

作为一个解决方案:我建议在 main 接收到停止信号时设置停止状态 _shouldStopThreadExecution。然后删除析构函数中 try.join() 语句,以便查看 quaranty 下线程的正确结尾。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?