将信号(SIGINT)传播到C++11线程

10
我正在尝试在接收到SIGINT信号(^C)时正确终止我的多线程C++11应用程序,但出于某些原因它不会传播到子线程,尽管主线程对其做出了响应。
例如(如下面的代码示例所示),如果在线程内部有一些阻塞函数(如sleep()),则如果您使用sigaction()函数安装了任何SIGNT挂钩,它将带走所有 ^C控制权。
有没有办法修复或解决这种行为? 有没有办法将主收到的信号传播到子线程? 编辑:用C++11的std :: this_thread :: sleep_for()替换了POSIX的sleep()
#include <iostream>
#include <thread>
#include <chrono>
#include <signal.h> // for sigaction() function 

static int signaled = 0;

void threaded_foo() {
  while (!signaled) {
    // pressing ^C now will not lead to correct termination, because we are
    // sleeping for a long time (100500 seconds) and do not respond to
    // SIGINT for some tricky reason i am looking a bypassage for.
    std::chrono::seconds duration(100500);
    std::this_thread::sleep_for(duration);
  }
  std::cout << "Correct termination\n";
}

void sighandler(int sig, siginfo_t *siginfo, void *context) {
  signaled = 1;
}

void install_sig_hooks() {
  struct sigaction action;
  memset(&action, 0, sizeof(struct sigaction));
  action.sa_sigaction = sighandler;
  action.sa_flags     = SA_SIGINFO;
  sigaction(SIGINT, &action, NULL);
}

int main(int argc, char **argv) {
  install_sig_hooks();

  std::thread t(threaded_foo);
  t.join();
  return 0;
}
编辑2 解决方案是将所有阻塞调用替换为非阻塞调用,并使用条件变量和轮询等机制。 实际问题仍未得到解答,因为当前软件(可能还包括硬件)不是设计成处理多个线程接收信号的方式,并且必须有一个线程告诉其他线程在接收信号后该做什么。

你为什么要将POSIX的sleep函数与C++11线程混合使用? - Ben Voigt
举个例子,实际应用中是使用ZeroMQ的send()函数,具有完全相同的效果。 - Alexander Tumin
很好,实际上,这是一个糟糕的例子,我应该使用std::this_thread::sleep_for()。抱歉,让我更正我的示例。 - Alexander Tumin
1个回答

5
我正在尝试在接收到SIGINT信号(^ C)时正确终止我的多线程C++11应用程序,但由于某种原因它不会传播到子线程,尽管主线程可以很好地响应它。 POSIX区分针对进程或线程的信号。在任一情况下,只有一个线程接收信号:
在生成信号时,应确定该信号是为进程还是进程内特定线程所生成。由某个特定线程引起的某些动作生成的信号(例如硬件故障)应为引起信号生成的线程生成。与进程ID、进程组ID或异步事件(例如终端活动)相关联生成的信号应为进程生成。
...
为进程生成的信号应传递到进程内正在调用选择该信号的sigwait()函数的那些线程之一,或者未阻止信号传递的线程之一。如果没有线程在调用选择该信号的sigwait()函数,并且如果进程内所有线程都阻止了信号传递,则该信号将保留在进程上,直到某个线程调用选择该信号的sigwait()函数、某个线程取消了信号传递阻止,或者与该信号关联的操作设置为忽略该信号。
在多线程进程中,一个常见的解决方案是在除一个线程外的所有线程中阻止处理进程信号。那个线程通常会处理所有进程信号并告诉其他线程该怎么做(例如终止)。
此外,由于 signaled 是由信号处理程序设置的,因此应该声明为 volatile。这是 volatile 介绍的两种用例之一。
static int volatile signaled = 0;

你的回答引发了两个新问题:1)如何在C++11中中断线程,使所有的sleep()都会被唤醒?2)如何使用C++11清单实际终止线程? - Alexander Tumin
1
@AlexanderTumin 我建议不要使用线程中断,因为你可能使用的第三方库可能无法正确处理中断。请礼貌地告诉你的线程终止并等待。这假定所有的线程都有某种事件循环,以便可以告诉它们终止。 - Maxim Egorushkin
1
你可以用条件变量的定时等待来替换 sleep()。然后,当你想让所有线程停止“睡眠”时,你可以向所有线程广播。 - John Zwinck
1
在这里使用 volatile 是不正确的。请参阅 http://www.drdobbs.com/parallel/volatile-vs-volatile/212701484 了解原因。你应该只使用 std::atomic<int>std::atomic<bool> 来表示信号标志。 - Nathan Ernst
1
@NathanErnst 请参阅C++ 2003 §1.9.9:当抽象机的处理被信号接收中断时,类型不是volatile sig_atomic_t的对象的值是未指定的,并且由处理程序修改的任何不是volatile sig_atomic_t的对象的值变为未定义。 在这里要注意,应该使用sig_atomic_t而不是int,但在Linux平台上,sig_atomic_tint - Maxim Egorushkin
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接