我有一个多线程C++程序。 主线程进行一些设置,然后创建几个子进程,用于处理来自 RabbitMQ 的传入消息。主线程中有忙等待解决方案:
while (rmqbag_active) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
在我还为 SIGINT 注册信号处理程序之前:
std::signal(SIGINT, signal_handler);
void signal_handler([[maybe_unused]] int signal) {
rmqbag_active = false;
rmqbag::log_debug("Received stopping signal");
}
当子进程中完成的工作量较少时,这也可以正常工作。 但是,当收到的消息量太高时(我不知道确切的阈值,但当四个子进程每秒收到大约 1,750 条消息时,就会出现问题),按 CTRL+C 不会执行任何操作。应用程序没有收到信号,因为没有日志输出。
现在,当我使用 HTOP 手动向进程发送 SIGINT 时,它会得到正确处理并且应用程序正常停止。
我已经用多个终端模拟器尝试过,bash、zsh 和 Fish shell 的行为似乎是相同的。 我还尝试了不同的方法来实现繁忙等待解决方案,例如使用
nanosleep
而不是 this_thread::sleep_for
。
为子进程注册信号处理程序也没有帮助。
我很困惑,我启动应用程序的终端为何并不总是向应用程序发送停止的用户输入。 HTOP 也只是发送信号并从那里获取进程,这表明这不是代码的问题。 我还应该提到的是,子进程不会记录任何内容,因此终端中不会出现垃圾邮件。
您知道为什么终端无法向进程发送信号吗?
问题在于信号处理程序的操作非常(非常!)有限:您可以在此处阅读列表
例如,我有理由确定这个
rmqbag::log_debug("Received stopping signal");
是未定义的行为。
据我了解,问题的一部分在于,您无法知道或控制哪个线程执行信号处理程序,而这只会导致一组极其有限的法律行动或大量的法律行动。或多或少有一些糟糕的边缘情况(又名 UB),这似乎就是您所经历的。
虽然有一个解决方案(可能有几个,但这就是我使用的)。通过使用一组 POSIX 函数,您实际上可以控制信号的处理位置,从而确保信号发生在不适用这些限制的上下文中。
第一步是关闭主线程的信号,您应该将其作为首要任务之一 - 至少在生成任何其他线程之前,因为它们从父线程继承信号掩码。
// Before any other thread is started
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT); // The one you want
sigaddset(&set, SIGTERM); // But you can add more if you want
sigaddset(&set, SIGUSR1);
sigaddset(&set, SIGCHLD);
int err = pthread_sigmask(SIG_BLOCK, &set, nullptr);
if (err != 0)
std::cout << "Error setting up signal handler" << std::endl;
然后你需要一个专门用于处理信号的线程 - 在我的例子中,我生成一个新线程。
// Note: Pass on exactly the set of signals that you just turned off
auto signalHandler = std::thread(sigHandler, &set);
信号处理程序线程然后使用您想要接收的信号集调用
sigwait
,这是一个阻塞调用,因此信号线程无法执行其他操作。
void sigHandler(sigset_t const *const set)
{
int sig; // Used as output variable
while (true)
{
sigwait(set, &sig);
switch (sig) // Examine which signal wait received
{
case SIGINT:
// Handle the signal
rmqbag_active = false;
rmqbag::log_debug("Received stopping signal");
break;
case SIGTERM:
// Other cases here to illustrate
// Do stuff
break;
case SIGUSR1:
break;
case SIGCHLD:
break;
default:
break;
}
}
}
所有这些都确保您选择的信号将在
signalHandler
线程中专门处理,而不是在其他地方,此外,它们将在“正常”代码中处理,而不是在不允许您执行任何操作的某些中断环境中处理.
您可以在这里阅读更多内容: