面试题答案
一键面试可能遇到的重入问题
- 全局变量冲突:信号处理函数可能会与线程函数同时访问和修改全局变量,导致数据不一致。例如,一个线程正在更新一个全局计数器,此时信号处理函数也尝试访问或修改该计数器,就会产生冲突。
- 函数重入:非可重入函数在信号处理函数和线程函数中同时被调用时,可能会出现问题。非可重入函数通常使用静态或全局数据结构,这些数据结构在多线程和信号处理的并发环境下容易被破坏。例如,
malloc
和printf
函数都不是可重入的,在信号处理函数中调用它们可能会导致程序崩溃。
避免重入问题的编程技巧
- 使用可重入函数:在信号处理函数中,只调用可重入函数。可重入函数不会使用静态或全局数据结构,或者在每次调用时都会创建自己的局部数据副本。例如,
strcpy
不是可重入的,而strncpy
是可重入的。read
和write
函数也是可重入的,适合在信号处理函数中使用。 - 信号掩码:在进入关键代码段之前,使用
sigprocmask
函数阻塞SIGINT信号,防止信号在关键操作过程中被处理。在关键操作完成后,再解除信号阻塞。这样可以保证关键代码段不会被信号处理函数打断,从而避免重入问题。
关键代码示例
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
// 定义全局变量表示程序是否应该退出
volatile sig_atomic_t quit = 0;
// 信号处理函数
void sigint_handler(int signum) {
quit = 1;
}
// 线程函数
void* thread_function(void* arg) {
while (!quit) {
// 线程的正常操作
printf("Thread is running...\n");
sleep(1);
}
printf("Thread is exiting...\n");
return NULL;
}
int main() {
pthread_t thread;
// 注册SIGINT信号处理函数
struct sigaction sa;
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return 1;
}
// 主线程等待线程结束
while (!quit) {
// 主线程的正常操作
printf("Main thread is running...\n");
sleep(1);
}
printf("Main thread is waiting for the thread to finish...\n");
pthread_join(thread, NULL);
printf("Program is exiting...\n");
return 0;
}
在这个示例中:
volatile sig_atomic_t quit
用于表示程序是否应该退出,volatile
关键字确保编译器不会对该变量进行优化,sig_atomic_t
类型保证该变量可以被信号处理函数安全地访问和修改。sigint_handler
函数是SIGINT信号的处理函数,它只设置quit
变量,这是一个简单且可重入的操作。- 在
main
函数中,通过sigaction
注册了信号处理函数。线程和主线程在quit
变量被设置之前会持续运行,当接收到SIGINT信号时,quit
变量被设置,线程和主线程会安全地退出。