0. 概要
在Linux编程中,信号是一种重要的IPC(进程间通信)机制。信号可以通知进程发生了某些事件,如终止进程、定时器到期等。除了常用的信号(如SIGINT
、SIGTERM
等),还有一些较为冷门的信号,可以用于自定义用途。
本文将简述如何在C++程序的业务线程中注册和处理这些信号。
1. 信号处理基础
POSIX信号有两类:标准信号和实时信号。标准信号包括我们常见的SIGUSR1
、SIGUSR2
、SIGINT
等,而实时信号则提供了更强的功能,比如可以传递附加信息。
在C++中,可以通过sigaction
函数来注册信号处理程序:
#include <iostream>
#include <csignal>
#include <atomic>
#include <thread>
#include <unistd.h>
// 全局变量,用于跨线程共享数据
std::atomic<int> timeout(0);
void signalHandler(int signum) {
if (signum == SIGUSR1) {
timeout.store(100);
std::cout << "Received SIGUSR1, timeout set to 100" << std::endl;
} else if (signum == SIGUSR2) {
timeout.store(200);
std::cout << "Received SIGUSR2, timeout set to 200" << std::endl;
}
}
void businessThread() {
// 设置信号处理程序
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = signalHandler;
if (sigaction(SIGUSR1, &action, nullptr) == -1) {
std::cerr << "Failed to register SIGUSR1 handler" << std::endl;
return;
}
if (sigaction(SIGUSR2, &action, nullptr) == -1) {
std::cerr << "Failed to register SIGUSR2 handler" << std::endl;
return;
}
// 模拟业务逻辑
while (true) {
std::cout << "Business thread running. Current timeout: " << timeout.load() << std::endl;
sleep(1); // 模拟一些工作
}
}
int main() {
// 启动业务线程
std::thread bizThread(businessThread);
// 主线程继续运行
while (true) {
// 主线程的其他逻辑
sleep(1); // 模拟一些工作
}
// 等待业务线程完成(此行在示例中不会实际运行到)
bizThread.join();
return 0;
}
2. 在业务线程中注册信号
在某些情况下,我们希望在特定线程中处理信号,而不是在主线程中。这可以通过在业务线程中注册信号处理程序来实现。
在上面的示例中,我们在businessThread
函数中注册了SIGUSR1
和SIGUSR2
信号处理程序。当收到这些信号时,信号处理程序将更新全局变量timeout
的值。业务线程会定期检查timeout
值,并根据需要进行处理。
3. 使用冷门信号
除了SIGUSR1
和SIGUSR2
,我们还可以使用一些不常用的信号,例如SIGXCPU
(超过CPU时间限制的信号)和SIGXFSZ
(超过文件大小限制的信号):
#include <iostream>
#include <csignal>
#include <atomic>
#include <thread>
#include <unistd.h>
std::atomic<int> timeout(0);
void signalHandler(int signum) {
if (signum == SIGXCPU) {
timeout.store(100);
std::cout << "Received SIGXCPU, timeout set to 100" << std::endl;
} else if (signum == SIGXFSZ) {
timeout.store(200);
std::cout << "Received SIGXFSZ, timeout set to 200" << std::endl;
}
}
void businessThread() {
// 设置信号处理程序
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = signalHandler;
if (sigaction(SIGXCPU, &action, nullptr) == -1) {
std::cerr << "Failed to register SIGXCPU handler" << std::endl;
return;
}
if (sigaction(SIGXFSZ, &action, nullptr) == -1) {
std::cerr << "Failed to register SIGXFSZ handler" << std::endl;
return;
}
// 模拟业务逻辑
while (true) {
std::cout << "Business thread running. Current timeout: " << timeout.load() << std::endl;
sleep(1); // 模拟一些工作
}
}
int main() {
// 启动业务线程
std::thread bizThread(businessThread);
// 主线程继续运行
while (true) {
// 主线程的其他逻辑
sleep(1); // 模拟一些工作
}
// 等待业务线程完成(此行在示例中不会实际运行到)
bizThread.join();
return 0;
}
4. 关于使用信号的注意事项
虽然信号在进程间通信中非常有用,但在多线程环境下使用信号时需要小心。信号处理程序应该尽量简单和快速,因为它们会中断正常的程序执行。此外,信号处理程序中不应使用可能阻塞的函数,例如malloc
或printf
。
更重要的是,信号处理在多线程环境中是一个复杂的主题。不同的线程库和操作系统可能会有不同的行为,特别是在处理实时信号时。因此,在多线程程序中使用信号处理时,建议尽量避免复杂的逻辑。
5. 更好的替代方案
本文是适用于为了快速实现某个功能,而不希望改动很多代码,在正式项目中更推荐使用其他方式进行通信和事件通知。
5.1 线程间通信
对于线程间通信,可以使用例如条件变量、消息队列或其他同步机制。它们通常比信号更易于使用和调试,并且在跨平台应用中更加可靠。
条件变量示例:
#include <iostream>
#include <thread>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <unistd.h>
std::atomic<int> timeout(0);
std::condition_variable cv;
std::mutex cv_m;
void businessThread() {
std::unique_lock<std::mutex> lk(cv_m);
while (true) {
cv.wait(lk, []{ return timeout.load() == 100 || timeout.load() == 200; });
std::cout << "Business thread running. Current timeout: " << timeout.load() << std::endl;
// Reset timeout for next wait
timeout.store(0);
}
}
int main() {
// 启动业务线程
std::thread bizThread(businessThread);
// 主线程模拟设置timeout
while (true) {
{
std::lock_guard<std::mutex> lk(cv_m);
timeout.store(100);
}
cv.notify_one();
sleep(5); // 模拟主线程的工作
}
// 等待业务线程完成(此行在示例中不会实际运行到)
bizThread.join();
return 0;
}
5.2 进程见通信
进程间通信建议选择domain socket、共享内容或ZeroMQ
比如ZeroMQ等高级消息传递库提供了跨语言、跨平台的高性能通信机制,非常适合复杂应用。
ZeroMQ示例(Python和C++):
Python发送端:
import zmq
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5555")
while True:
socket.send_string("Hello from Python!")
time.sleep(1)
C++接收端:
#include <zmq.hpp>
#include <iostream>
int main() {
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_SUB);
socket.connect("tcp://localhost:5555");
socket.setsockopt(ZMQ_SUBSCRIBE, "", 0);
while (true) {
zmq::message_t message;
socket.recv(&message);
std::string msg_str(static_cast<char*>(message.data()), message.size());
std::cout << "Received: " << msg_str << std::endl;
}
return 0;
}