面试题答案
一键面试可能遇到的问题
- 信号处理函数执行上下文:信号处理函数在一个异步的上下文中执行,可能会打断子线程的正常执行流程,导致共享数据的访问冲突。
- 线程安全性:由于主线程屏蔽了信号,子线程注册处理函数,不同线程对信号处理的交互可能导致线程安全问题,例如信号在主线程屏蔽期间到达,当主线程解除屏蔽时,信号处理可能在子线程中意外触发,访问未保护的共享资源。
- 可重入性:信号处理函数必须是可重入的,即它可以被中断并再次进入而不会导致数据损坏或其他未定义行为。如果处理函数中调用了不可重入的函数,会引发问题。
解决方法
- 使用线程特定数据(TSD):通过设置线程特定数据来保护共享资源,确保信号处理函数中对共享资源的访问是安全的。
- 互斥锁(Mutex):在访问共享资源前后加锁,防止多个线程同时访问共享资源,避免数据竞争。
- 选择可重入函数:在信号处理函数中只调用可重入的函数,例如
_exit
、write
等。
代码示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
// 定义互斥锁
pthread_mutex_t mutex;
// 共享数据
int shared_data = 0;
// 信号处理函数
void sigterm_handler(int signum) {
// 加锁
pthread_mutex_lock(&mutex);
printf("SIGTERM received in child thread. Shared data: %d\n", shared_data);
// 解锁
pthread_mutex_unlock(&mutex);
// 使用可重入函数退出程序
_exit(EXIT_SUCCESS);
}
void* child_thread(void* arg) {
// 注册SIGTERM信号处理函数
struct sigaction sa;
sa.sa_handler = sigterm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) == -1) {
perror("sigaction");
pthread_exit(NULL);
}
while (1) {
// 模拟子线程工作
pthread_mutex_lock(&mutex);
shared_data++;
printf("Child thread incremented shared data: %d\n", shared_data);
pthread_mutex_unlock(&mutex);
sleep(1);
}
pthread_exit(NULL);
}
int main() {
pthread_t tid;
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 屏蔽SIGTERM信号
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGTERM);
if (pthread_sigmask(SIG_BLOCK, &set, NULL) == -1) {
perror("pthread_sigmask");
return EXIT_FAILURE;
}
// 创建子线程
if (pthread_create(&tid, NULL, child_thread, NULL) != 0) {
perror("pthread_create");
return EXIT_FAILURE;
}
// 主线程等待子线程结束
if (pthread_join(tid, NULL) != 0) {
perror("pthread_join");
return EXIT_FAILURE;
}
// 主线程清理
pthread_mutex_destroy(&mutex);
return EXIT_SUCCESS;
}
此代码通过以下方式解决问题:
- 使用互斥锁
pthread_mutex_t
保护共享数据shared_data
,确保在信号处理函数和子线程正常执行时,对共享数据的访问是线程安全的。 - 信号处理函数
sigterm_handler
中使用可重入函数_exit
安全地退出程序,避免在处理函数中调用不可重入函数导致的问题。 - 主线程屏蔽
SIGTERM
信号,子线程注册信号处理函数,同时处理函数中通过互斥锁保证共享数据访问的安全性。