MST

星途 面试题库

面试题:C语言信号处理中的竞态条件及解决方法

在Linux C语言信号处理机制中,常常会遇到竞态条件问题。请描述在捕获和处理信号时可能出现竞态条件的场景,并给出至少一种有效的解决办法,同时结合代码示例说明如何避免这些问题。
33.0万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

可能出现竞态条件的场景

  1. 信号处理函数与主程序共享全局变量:当主程序和信号处理函数都访问和修改同一个全局变量时,可能导致竞态条件。例如,主程序正在读取全局变量的值进行某些计算,此时信号到来,信号处理函数修改了该全局变量,主程序的计算结果就可能出现错误。
  2. 信号处理函数调用不可重入函数:不可重入函数不能被多个进程或线程安全地同时调用。如果信号处理函数调用了不可重入函数,而主程序也在调用该函数,就会产生竞态条件。例如,printf函数就是不可重入的,如果信号处理函数和主程序同时调用printf,可能导致输出混乱。
  3. 信号在系统调用过程中到来:某些系统调用(如readwrite等)在执行过程中可能被信号中断。如果在系统调用被中断后,没有正确处理,重新调用该系统调用可能会出现问题。例如,read函数读取文件时被信号中断,重新调用read可能会从错误的位置开始读取。

解决办法

  1. 使用可重入函数:在信号处理函数中,只调用可重入函数。可重入函数在被多个进程或线程调用时不会出现数据竞争问题。例如,strncat是不可重入的,而strncpy是可重入的。
  2. 使用全局标志变量:在主程序和信号处理函数之间通过全局标志变量进行通信。信号处理函数设置标志变量,主程序在合适的时机检查该标志变量并进行相应处理,避免在信号处理函数中进行复杂操作。
  3. 使用sigprocmask函数:通过sigprocmask函数阻塞信号,在需要处理信号的关键代码段前后分别设置信号阻塞和解除阻塞,确保在关键代码段执行期间不会收到信号,从而避免竞态条件。

代码示例

以下是使用sigprocmask函数避免竞态条件的示例代码:

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

volatile sig_atomic_t flag = 0;

void signal_handler(int signum) {
    flag = 1;
}

int main() {
    struct sigaction sa;
    sigset_t mask, oldmask;

    // 初始化信号处理函数
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);

    // 阻塞SIGINT信号
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigprocmask(SIG_BLOCK, &mask, &oldmask);

    // 关键代码段
    for (int i = 0; i < 10; i++) {
        printf("Counting: %d\n", i);
        sleep(1);

        // 检查是否收到SIGINT信号
        sigprocmask(SIG_SETMASK, &oldmask, NULL);
        if (flag) {
            printf("Received SIGINT, cleaning up...\n");
            flag = 0;
            // 重新阻塞SIGINT信号
            sigprocmask(SIG_BLOCK, &mask, NULL);
        } else {
            // 继续阻塞SIGINT信号
            sigprocmask(SIG_BLOCK, &mask, NULL);
        }
    }

    // 解除信号阻塞
    sigprocmask(SIG_SETMASK, &oldmask, NULL);

    return 0;
}

在上述代码中,通过sigprocmask函数在关键代码段阻塞SIGINT信号,在关键代码段执行过程中不会收到该信号,从而避免了竞态条件。当检查到SIGINT信号到来时,进行相应处理并重新阻塞信号,确保程序的正确运行。