如何确保子线程触发的Signal由主线程处理?
让信号处理始终在主线程执行的解决方案
要实现信号无论由外部触发还是子线程触发,都由主线程处理,核心思路是让所有子线程屏蔽目标信号,仅主线程保留接收权限,这样内核只会将信号递送到主线程,同时不需要额外创建线程。以下是两种可行方案:
方案一:基于信号掩码+信号处理函数
通过设置信号掩码,让子线程继承屏蔽SIGINT的配置,仅主线程解除屏蔽,确保信号处理函数在主线程执行:
#include <csignal> #include <iostream> #include <thread> #include <chrono> #include <mutex> #include <pthread.h> #include <unistd.h> std::thread::id main_id; std::mutex cout_mutex; // 避免多线程输出混乱 void signal_handler(int sig) { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << std::this_thread::get_id() << " got signal\n"; // 恢复默认信号处理,避免重复触发 signal(sig, SIG_DFL); exit(sig); } void thread_fn(pthread_t main_thread) { while (true) { { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "subthread " << std::this_thread::get_id() << " printing\n"; } std::this_thread::sleep_for(std::chrono::seconds(5)); // 向主线程发送SIGINT,而非给自己发送 pthread_kill(main_thread, SIGINT); } } int main() { // 1. 主线程先屏蔽SIGINT sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); pthread_sigmask(SIG_BLOCK, &mask, nullptr); // 2. 注册信号处理函数 signal(SIGINT, signal_handler); // 3. 获取主线程的pthread句柄,传递给子线程 pthread_t main_thread = pthread_self(); main_id = std::this_thread::get_id(); // 4. 创建子线程,子线程自动继承信号掩码(屏蔽SIGINT) std::thread t(&thread_fn, main_thread); // 5. 主线程解除SIGINT屏蔽,成为唯一能接收该信号的线程 pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); while (true) { { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "main " << main_id << " printing\n"; } std::this_thread::sleep_for(std::chrono::seconds(5)); } t.join(); return 0; }
关键说明:
- 子线程继承主线程的信号掩码,因此无法接收SIGINT,避免信号处理函数在子线程执行
- 外部Ctrl+C触发的SIGINT会被内核递送到唯一未屏蔽该信号的主线程
- 子线程需要触发信号时,通过
pthread_kill直接向主线程发送,而非使用raise给自己发送
方案二:基于sigwait(更安全)
sigwait可以让主线程在循环中主动等待信号,避免在信号处理函数中调用非异步安全的函数(如cout),更符合POSIX安全规范:
#include <csignal> #include <iostream> #include <thread> #include <chrono> #include <mutex> #include <pthread.h> #include <errno.h> std::thread::id main_id; std::mutex cout_mutex; void thread_fn(pthread_t main_thread) { while (true) { { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "subthread " << std::this_thread::get_id() << " printing\n"; } std::this_thread::sleep_for(std::chrono::seconds(5)); pthread_kill(main_thread, SIGINT); } } int main() { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); // 主线程屏蔽SIGINT,子线程继承该配置 pthread_sigmask(SIG_BLOCK, &mask, nullptr); pthread_t main_thread = pthread_self(); main_id = std::this_thread::get_id(); std::thread t(&thread_fn, main_thread); int sig; struct timespec timeout = {5, 0}; // 5秒超时,匹配原sleep逻辑 while (true) { { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << "main " << main_id << " printing\n"; } // 等待SIGINT,超时则继续循环 int ret = sigtimedwait(&mask, nullptr, &timeout); if (ret == SIGINT) { std::lock_guard<std::mutex> lock(cout_mutex); std::cout << main_id << " got signal\n"; exit(SIGINT); } else if (ret == -1 && errno == EAGAIN) { continue; } } t.join(); return 0; }
关键说明:
- 主线程通过
sigtimedwait等待信号,超时时间与原逻辑的sleep一致,不阻塞程序正常执行 - 无需注册信号处理函数,所有信号处理逻辑都在主线程的循环中完成,避免异步安全问题
内容的提问来源于stack exchange,提问作者nothingisme
相关产品推荐
相关产品推荐

