如何确保子线程发出的信号被主线程处理?

问题描述 投票:0回答:1

我的代码以多线程运行。同时,还有一个

signal_handler
函数处理外部
ctrl-c
信号。

我发现

signal_handler
函数在
raise
信号的线程上运行。

但我想在主线程上处理它,无论是谁

raise

这是一个演示代码:

#include <csignal>
#include <iostream>
#include <thread>

std::thread::id main_id;

void signal_handler(int sig) {
  // here always run in subthread, I want to make it run in main thread 
  std::thread::id this_id = std::this_thread::get_id();
  std::cout << this_id << " got signal\n";
  signal(sig, SIG_DFL);
  exit(sig);
}

void thread_fn() { 
  while (1) { 
    std::cout << "subthread " << std::this_thread::get_id() << " printing\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    raise(SIGINT);
  } 
}

int main() { 
  signal(SIGINT, signal_handler);
  std::thread t(&thread_fn);

  main_id = std::this_thread::get_id();
  while (1) { 
    std::cout << "main " << main_id << " printing\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
  } 

  t.join();
}

你能帮忙吗?我非常小气,这意味着我不想创建单个线程来等待线程。

c++ multithreading signals
1个回答
0
投票

带有 pthreads 的 POSIX(Linux、FreeBSD、macOS 等)

从技术上讲,信号被传递到各个线程而不是进程本身。为了在另一个线程上调用信号处理程序,您需要使用

pthread_kill()
并提供应执行信号处理程序的线程的 id。

因此,如果我稍微修改一下您的代码以使用低级函数(因为

std::thread
周围没有基于
pthread_kill()
的包装器):

#include <csignal>
#include <iostream>
#include <thread>

#include <pthread.h>

pthread_t main_thread_raw_id;

void signal_handler(int sig)
{
  // ... [see comments below]
}

void thread_fn() { 
  while (1) { 
    std::cout << "subthread " << std::this_thread::get_id() << " printing\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
    pthread_kill(main_thread_raw_id, SIGINT);
  } 
}

int main() { 
  signal(SIGINT, signal_handler);
  std::thread t(&thread_fn);

  main_thread_raw_id = pthread_self();
  auto main_id = std::this_thread::get_id();
  while (1) { 
    std::cout << "main " << main_id << " printing\n";
    std::this_thread::sleep_for(std::chrono::seconds(5));
  } 

  t.join();
}

然后主线程将收到信号。 (请注意,

std::this_thread::get_id()
pthread_self()
都被视为不透明,并且不能保证它们彼此相等,即使它们可能位于您的系统上。)

大家都说:

正如其他人指出的那样,在信号处理程序中使用

cout
并不安全,可能会导致各种问题。这适用于您可能使用的许多函数,从堆上分配的所有函数(通过
new
malloc()
)开始,一直到互斥锁等。即使
exit()
也不安全,只有
_exit()
是安全的,因为
 exit()
可以调用用
atexit()
注册的析构函数(以及 C++ 析构函数),而
_exit()
会跳过这些。

所有这些限制使得信号处理程序很难正确编写,大多数使用它们的人只是设置一个原子标志或类似的东西,这样很容易推断处理程序本身是安全的。

更进一步,除非是一些非常具体的信号,例如

SIGTSTP
在实践中不可行,否则 Linux 上的许多现代软件甚至不想使用信号处理程序,而是使用事件注册
signalfd()
循环。

Windows

在 Windows 上,你完全不走运:Microsoft 实现

signal()
只是因为 C 标准要求他们这样做 - 而且他们实现方式不同:

  • SIGTERM
    /
    SIGILL
    永远不会生成
  • 每当通过代码发出生成信号条件时,信号处理程序都会在该线程本身内执行。
  • 当使用
    abort()
    raise(signal)
    时,该线程将执行信号处理程序。
  • 在控制台中按 Ctrl+C 调用
    SIGINT
    时,将在 Windows 上创建一个新线程来调用信号处理程序。

基本上,任何仅在 C 标准定义的范围内使用

signal()
的程序都可以与 Microsoft 的实现一起使用,但任何依赖于实际 POSIX 语义的程序都不会。 Windows 提供了大量其他与线程交互的机制(从 APC 到挂起和直接修改线程的执行状态),您可能可以以某种方式模拟行为,但 Windows 坚决不实现 POSIX 线程语义。

请参阅此问题中的评论了解更多详细信息。

结束语

您似乎想在这里使用信号作为某种线程同步机制。虽然这当然是可能的,并且在某些边缘情况下它甚至可能有用(例如,当涉及实时线程时),但我强烈建议不要为此目的使用信号处理程序。

您想要解决的实际问题是什么?

© www.soinside.com 2019 - 2024. All rights reserved.