面试题答案
一键面试可能出现竞态条件的场景
- 信号处理函数与主程序共享全局变量:当主程序和信号处理函数都访问和修改同一个全局变量时,可能导致竞态条件。例如,主程序正在读取全局变量的值进行某些计算,此时信号到来,信号处理函数修改了该全局变量,主程序的计算结果就可能出现错误。
- 信号处理函数调用不可重入函数:不可重入函数不能被多个进程或线程安全地同时调用。如果信号处理函数调用了不可重入函数,而主程序也在调用该函数,就会产生竞态条件。例如,
printf
函数就是不可重入的,如果信号处理函数和主程序同时调用printf
,可能导致输出混乱。 - 信号在系统调用过程中到来:某些系统调用(如
read
、write
等)在执行过程中可能被信号中断。如果在系统调用被中断后,没有正确处理,重新调用该系统调用可能会出现问题。例如,read
函数读取文件时被信号中断,重新调用read
可能会从错误的位置开始读取。
解决办法
- 使用可重入函数:在信号处理函数中,只调用可重入函数。可重入函数在被多个进程或线程调用时不会出现数据竞争问题。例如,
strncat
是不可重入的,而strncpy
是可重入的。 - 使用全局标志变量:在主程序和信号处理函数之间通过全局标志变量进行通信。信号处理函数设置标志变量,主程序在合适的时机检查该标志变量并进行相应处理,避免在信号处理函数中进行复杂操作。
- 使用
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
信号到来时,进行相应处理并重新阻塞信号,确保程序的正确运行。