Linux编程: 在业务线程中注册和处理Linux信号

24 篇文章 0 订阅

0. 概要

在Linux编程中,信号是一种重要的IPC(进程间通信)机制。信号可以通知进程发生了某些事件,如终止进程、定时器到期等。除了常用的信号(如SIGINTSIGTERM等),还有一些较为冷门的信号,可以用于自定义用途。
本文将简述如何在C++程序的业务线程中注册和处理这些信号。

1. 信号处理基础

POSIX信号有两类:标准信号和实时信号。标准信号包括我们常见的SIGUSR1SIGUSR2SIGINT等,而实时信号则提供了更强的功能,比如可以传递附加信息。

在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函数中注册了SIGUSR1SIGUSR2信号处理程序。当收到这些信号时,信号处理程序将更新全局变量timeout的值。业务线程会定期检查timeout值,并根据需要进行处理。

3. 使用冷门信号

除了SIGUSR1SIGUSR2,我们还可以使用一些不常用的信号,例如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. 关于使用信号的注意事项

虽然信号在进程间通信中非常有用,但在多线程环境下使用信号时需要小心。信号处理程序应该尽量简单和快速,因为它们会中断正常的程序执行。此外,信号处理程序中不应使用可能阻塞的函数,例如mallocprintf

更重要的是,信号处理在多线程环境中是一个复杂的主题。不同的线程库和操作系统可能会有不同的行为,特别是在处理实时信号时。因此,在多线程程序中使用信号处理时,建议尽量避免复杂的逻辑。

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值