面试题答案
一键面试可能产生竞态条件的场景
- 信号处理函数与主程序共享全局变量:当主程序和信号处理函数都访问和修改同一个全局变量时,由于信号可能在主程序执行的任何时刻到达,就可能导致数据不一致。例如,主程序正在对一个计数器变量进行递增操作,在递增操作执行到一半时信号到达,信号处理函数也对该计数器进行操作,就会出现竞态。
- 异步信号打断系统调用:如果主程序正在执行一个慢速系统调用(如
read
、write
、accept
等),此时信号到达,系统调用可能被中断,信号处理函数执行完毕后,系统调用可能需要重新执行。如果在信号处理函数中修改了与系统调用相关的状态,就可能导致竞态。
避免竞态条件的方法及代码示例
方法一:使用互斥锁(Mutex)
通过互斥锁来保护共享资源,确保同一时间只有一个执行流可以访问共享资源。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
pthread_mutex_t mutex;
volatile int shared_variable = 0;
void signal_handler(int signum) {
pthread_mutex_lock(&mutex);
shared_variable++;
pthread_mutex_unlock(&mutex);
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
pthread_mutex_init(&mutex, NULL);
for (int i = 0; i < 5; i++) {
pthread_mutex_lock(&mutex);
shared_variable++;
printf("In main, shared_variable: %d\n", shared_variable);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_mutex_destroy(&mutex);
return 0;
}
在上述代码中,pthread_mutex_lock
和pthread_mutex_unlock
函数用于保护shared_variable
的访问,确保无论是主程序还是信号处理函数对其操作时都不会出现竞态。
方法二:使用volatile关键字和标志位
使用volatile
关键字修饰共享变量,并且通过设置一个标志位来告知信号处理函数和主程序当前的状态,避免同时访问共享资源。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
volatile int shared_variable = 0;
volatile sig_atomic_t flag = 0;
void signal_handler(int signum) {
if (!flag) {
flag = 1;
shared_variable++;
flag = 0;
}
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
for (int i = 0; i < 5; i++) {
if (!flag) {
flag = 1;
shared_variable++;
printf("In main, shared_variable: %d\n", shared_variable);
flag = 0;
}
sleep(1);
}
return 0;
}
这里volatile sig_atomic_t
类型的flag
作为一个标志位,确保主程序和信号处理函数不会同时访问shared_variable
,volatile
关键字保证对flag
和shared_variable
的访问不会被编译器优化而导致意外结果。