【发布时间】:2021-07-10 05:37:36
【问题描述】:
我要做什么
我有各种必须在 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(取决于我使用的哪个),然后程序结束,这很好。 然而:
- 清理涉及将某些内容写入文件(在执行过程中已成功写入该文件),但似乎并非总是如此,这意味着清理并未始终完全执行,即一个问题
- 程序的返回码不是预期的 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的结果,而不是我的程序的
我将重新编写脚本,但是两个信号都发送到我的程序这一事实是否会导致有时清理失败?如何?我该怎么办?
【问题讨论】:
-
我目前正在处理类似的问题/设置。我喜欢带有内部条件变量的控制循环,用于控制锁定和唤醒条件。检查内部线程执行状态的析构函数也是一个好主意。可能会适应这个想法。谢谢
-
我刚刚从en.cppreference.com/w/cpp/utility/program/signal 中读到了一些关于
std::signal的内容,显然你传递了一个非法的信号处理函数。 -
@ALX23z 怎么样?它说我可以将它传递给
SIG_DFL、SIG_IGN,或者一个指向签名等效于extern "C" void fun(int sig);的函数的指针。我假设我的静态函数有正确的签名,你是说这里有一些名字修改吗? -
@ALX23z 或者你是说我在函数中进行非法操作?与 std::atomic 交互没问题……等等,正在打印一些未定义的行为吗?
-
请使用
sigaction和pthread_sigmask明确定义信号处理行为并将处理程序执行限制为单个线程。您没有在代码或复制步骤中向我们展示某些内容 - 但sigaction和掩码将使一切更容易理解。 (例如,如果您有两个连续的 SIGINT 并且您的系统的signal在处理程序调用时将处置恢复为 SIG_DFL,则您可能会遇到信号死亡。)
标签: c++ linux multithreading signals sigterm