MST

星途 面试题库

面试题:C语言信号与多线程交互的深度探讨

在多线程环境下使用C语言进行编程,线程与信号的交互较为复杂。请阐述Linux中信号在多线程程序中的行为特性,如信号如何在多个线程间分配,以及如何确保信号处理的正确性和线程安全。请详细说明相关的POSIX线程信号函数,并结合一个具体的多线程程序示例,展示如何在多线程中安全地处理信号。
32.2万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

Linux中信号在多线程程序中的行为特性

  1. 信号分配
    • 在多线程程序中,信号通常发送到进程,而不是特定线程。如果没有特殊设置,任何线程都可能接收到信号。但是,可以通过设置线程掩码(使用pthread_sigmask函数)来控制哪个线程可以接收信号。
    • 某些信号(如SIGKILLSIGSTOP)是不能被捕获或忽略的,并且会立即终止进程,与线程设置无关。
  2. 确保信号处理的正确性和线程安全
    • 信号处理函数应该是可重入的。这意味着信号处理函数在被调用时,不应该依赖于可能正在被其他线程修改的全局或静态数据结构,避免数据竞争。
    • 使用线程特定数据(TSD,Thread - Specific Data)来确保每个线程有自己独立的数据副本,从而避免在信号处理时与其他线程的数据冲突。
    • 可以通过阻塞信号(使用pthread_sigmask),在特定线程中安全地处理信号,防止信号在不合适的时候中断线程的关键操作。

POSIX线程信号函数

  1. pthread_sigmask
    • 功能:用于设置线程的信号掩码,决定线程是否阻塞某些信号。
    • 原型:int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
    • 参数:
      • how:指定如何修改信号掩码,有SIG_BLOCK(阻塞信号集中的信号)、SIG_UNBLOCK(解除阻塞信号集中的信号)、SIG_SETMASK(将信号掩码设置为指定的信号集)。
      • set:指向要操作的信号集。
      • oldset:指向一个信号集,用于保存原来的信号掩码(可以为NULL)。
    • 返回值:成功返回0,失败返回错误码。
  2. pthread_kill
    • 功能:向指定线程发送信号。
    • 原型:int pthread_kill(pthread_t thread, int sig);
    • 参数:
      • thread:目标线程的ID。
      • sig:要发送的信号。
    • 返回值:成功返回0,失败返回错误码。
  3. sigwait
    • 功能:等待信号集里的信号到达,线程会阻塞直到有信号到达。
    • 原型:int sigwait(const sigset_t *set, int *sig);
    • 参数:
      • set:指向要等待的信号集。
      • sig:用于返回接收到的信号值。
    • 返回值:成功返回0,失败返回错误码。

多线程中安全处理信号的示例代码

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 信号处理函数
void *signal_handler(void *arg) {
    sigset_t *set = (sigset_t *)arg;
    int sig;
    while (1) {
        // 等待信号
        if (sigwait(set, &sig) != 0) {
            perror("sigwait");
            pthread_exit(NULL);
        }
        switch (sig) {
        case SIGINT:
            printf("Received SIGINT in signal handler thread\n");
            break;
        case SIGTERM:
            printf("Received SIGTERM in signal handler thread\n");
            pthread_exit(NULL);
        default:
            printf("Received unknown signal %d in signal handler thread\n", sig);
        }
    }
    return NULL;
}

int main() {
    pthread_t signal_thread;
    sigset_t set;

    // 初始化信号集
    sigemptyset(&set);
    // 添加要处理的信号
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGTERM);

    // 阻塞主线程中的信号
    if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) {
        perror("pthread_sigmask");
        return 1;
    }

    // 创建信号处理线程
    if (pthread_create(&signal_thread, NULL, signal_handler, (void *)&set) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 主线程继续执行其他任务
    while (1) {
        printf("Main thread is running...\n");
        sleep(1);
    }

    // 等待信号处理线程结束
    if (pthread_join(signal_thread, NULL) != 0) {
        perror("pthread_join");
        return 1;
    }

    return 0;
}

在这个示例中:

  1. 主线程阻塞了SIGINTSIGTERM信号。
  2. 创建了一个专门的线程来处理这两个信号,使用sigwait等待信号到达并进行处理。这样可以确保信号处理的线程安全性,避免信号在主线程执行关键操作时打断主线程。