You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过pthread_kill在pthread_t间传递自定义指针并区分超时信号?

用pthread_kill结合SA_SIGINFO传递自定义指针区分超时信号

刚好之前在项目里处理过类似的线程间信号带自定义数据的场景,我来给你捋清楚怎么实现——核心是结合SA_SIGINFO信号处理机制和sigqueue(而非单纯的pthread_kill)传递自定义指针,因为pthread_kill只能给线程发信号,没法携带额外数据,而sigqueue可以通过struct sigval传递指针。

核心思路拆解

  1. 注册支持附加信息的信号处理函数(启用SA_SIGINFO标志)
  2. 定义自定义数据结构体,用来区分不同的超时操作(比如read/listen的标识、上下文)
  3. 用sigqueue给目标线程发送信号,同时把自定义指针塞进sigval里
  4. 在信号处理函数中,从siginfo_t结构体取出sigval,转成自定义指针后做区分逻辑

具体实现步骤&代码示例

1. 定义自定义数据结构

这个结构体用来承载你需要区分的属性,比如操作类型、上下文信息:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>

// 获取线程的内核级ID(Linux下需要,因为sigqueue用的是内核tid)
#define gettid() syscall(SYS_gettid)

// 自定义超时数据:用来区分不同的阻塞操作
typedef struct {
    int op_type;  // 标识操作类型:0=read,1=listen,可自定义
    void *ctx;    // 操作相关的上下文,比如文件描述符、连接信息等
} TimeoutData;

2. 注册带SA_SIGINFO的信号处理函数

这个函数能拿到信号的附加信息,也就是我们传递的自定义指针:

void timeout_handler(int sig, siginfo_t *info, void *ucontext) {
    // 从siginfo中取出我们传递的自定义数据
    TimeoutData *timeout_info = (TimeoutData *)info->si_value.sival_ptr;
    
    // 注意:信号处理函数里只能调用**异步信号安全**的函数!
    // 比如不能用printf,改用write到stderr
    char log_msg[64];
    int msg_len;
    
    if (timeout_info->op_type == 0) {
        msg_len = snprintf(log_msg, sizeof(log_msg), "Read操作超时 | 上下文: %p\n", timeout_info->ctx);
    } else if (timeout_info->op_type == 1) {
        msg_len = snprintf(log_msg, sizeof(log_msg), "Listen操作超时 | 上下文: %p\n", timeout_info->ctx);
    } else {
        msg_len = snprintf(log_msg, sizeof(log_msg), "未知操作超时 | 上下文: %p\n", timeout_info->ctx);
    }
    write(STDERR_FILENO, log_msg, msg_len);
    
    // 如果是动态分配的timeout_info,这里可以释放(确保后续不会再访问)
    free(timeout_info);
}

3. 工作线程(执行阻塞操作的线程)

工作线程需要把自己的内核tid传递给发送信号的线程,这样才能用sigqueue精准发信号:

void *blocking_worker(void *arg) {
    pid_t my_tid = gettid();
    printf("工作线程内核tid: %d\n", my_tid);
    
    // 把tid传给主线程(这里用参数指针传递,也可以用全局变量/队列)
    *(pid_t *)arg = my_tid;
    
    // 模拟阻塞操作(比如read、accept等)
    while (1) {
        sleep(10);  // 代替实际的阻塞系统调用
    }
    
    return NULL;
}

4. 主线程(发送超时信号的线程)

主线程创建工作线程,获取其tid后,用sigqueue发送带自定义数据的信号:

int main() {
    // 配置信号处理
    struct sigaction sa;
    sa.sa_sigaction = timeout_handler;
    sa.sa_flags = SA_SIGINFO;  // 关键:启用带附加信息的信号处理
    sigemptyset(&sa.sa_mask);  // 处理信号时屏蔽其他信号(可选)
    
    // 注册自定义超时信号(比如用SIGUSR1,避免用系统默认信号)
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction注册失败");
        exit(EXIT_FAILURE);
    }
    
    // 创建工作线程
    pid_t worker_tid;
    pthread_t worker_thread;
    if (pthread_create(&worker_thread, NULL, blocking_worker, &worker_tid) != 0) {
        perror("创建工作线程失败");
        exit(EXIT_FAILURE);
    }
    
    // 等待工作线程启动并传递tid
    sleep(1);
    
    // 模拟给read操作发送超时信号
    TimeoutData *read_timeout = malloc(sizeof(TimeoutData));
    read_timeout->op_type = 0;
    read_timeout->ctx = (void *)0x1234;  // 这里可以传实际的文件描述符等
    
    union sigval sig_data;
    sig_data.sival_ptr = read_timeout;
    if (sigqueue(worker_tid, SIGUSR1, sig_data) == -1) {
        perror("发送read超时信号失败");
        free(read_timeout);
        exit(EXIT_FAILURE);
    }
    
    // 模拟给listen操作发送超时信号
    sleep(1);
    TimeoutData *listen_timeout = malloc(sizeof(TimeoutData));
    listen_timeout->op_type = 1;
    listen_timeout->ctx = (void *)0x5678;
    
    sig_data.sival_ptr = listen_timeout;
    if (sigqueue(worker_tid, SIGUSR1, sig_data) == -1) {
        perror("发送listen超时信号失败");
        free(listen_timeout);
        exit(EXIT_FAILURE);
    }
    
    // 等待信号处理完成
    sleep(2);
    
    // 清理线程
    pthread_cancel(worker_thread);
    pthread_join(worker_thread, NULL);
    
    return 0;
}

关键注意事项

  • 为什么不用pthread_kill?:pthread_kill的原型是int pthread_kill(pthread_t thread, int sig);,它只能给线程发送信号,但无法携带sigval附加数据,所以要传递自定义指针必须用sigqueue,且目标是线程的内核tid(不是用户态的pthread_t)。
  • 异步信号安全:信号处理函数里绝对不能调用非异步安全的函数(比如printf、malloc),如果要输出日志,尽量用write这种明确异步安全的系统调用。
  • 数据生命周期:如果自定义数据是动态分配的,一定要确保信号处理函数使用它的时候,数据没有被释放;要么在处理函数里释放,要么等确认信号已经处理完成后再释放,避免悬空指针。
  • 线程tid的传递:因为sigqueue需要目标线程的内核tid,所以工作线程必须把自己的tid传递给发送线程,传递方式可以是参数、全局变量、线程间消息队列等,根据你的场景选择。

内容的提问来源于stack exchange,提问作者torae

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:34:16