面试题答案
一键面试避免线程竞争和死锁及提高性能的线程模型设计
- 合理使用互斥锁
- 关键资源保护:在多线程访问共享资源(如共享内存区域、文件描述符等)时,对每个共享资源使用一个互斥锁。例如,若多个线程需要读写一个全局的连接池数据结构,可定义一个互斥锁
pthread_mutex_t pool_mutex;
,在访问连接池前通过pthread_mutex_lock(&pool_mutex);
锁定互斥锁,访问结束后使用pthread_mutex_unlock(&pool_mutex);
解锁。 - 粒度控制:尽量减小互斥锁的粒度。比如,若连接池中有不同部分的数据(如空闲连接列表和已使用连接列表),可考虑为每个部分分别设置互斥锁,而不是对整个连接池使用一个大的互斥锁,这样能减少线程等待时间,提高并发性能。
- 关键资源保护:在多线程访问共享资源(如共享内存区域、文件描述符等)时,对每个共享资源使用一个互斥锁。例如,若多个线程需要读写一个全局的连接池数据结构,可定义一个互斥锁
- 条件变量的使用
- 线程间同步:当某个线程需要等待某个条件满足才能继续执行时,可使用条件变量。例如,在线程池场景下,若任务队列空了,工作线程需要等待新任务到来。定义条件变量
pthread_cond_t task_cond;
和互斥锁pthread_mutex_t task_mutex;
。工作线程在任务队列空时执行:
- 线程间同步:当某个线程需要等待某个条件满足才能继续执行时,可使用条件变量。例如,在线程池场景下,若任务队列空了,工作线程需要等待新任务到来。定义条件变量
pthread_mutex_lock(&task_mutex);
while (task_queue_is_empty()) {
pthread_cond_wait(&task_cond, &task_mutex);
}
// 获取任务并处理
pthread_mutex_unlock(&task_mutex);
- 信号通知:当有新任务加入任务队列时,持有
task_mutex
锁的线程(如主线程接收新连接时创建新任务加入队列)通过pthread_cond_signal(&task_cond);
通知等待在条件变量上的工作线程。若有多个工作线程等待,也可使用pthread_cond_broadcast(&task_cond);
通知所有等待线程。
- 避免死锁
- 加锁顺序一致:确保所有线程以相同顺序获取多个互斥锁。例如,若线程有时需要同时获取
mutex1
和mutex2
,则所有线程都应先获取mutex1
,再获取mutex2
。 - 避免嵌套锁:尽量避免在持有一个锁的情况下获取另一个锁,除非绝对必要。若必须嵌套,要仔细设计锁的获取和释放逻辑,防止死锁。
- 使用超时机制:在获取锁时可使用带超时的函数,如
pthread_mutex_timedlock()
。若在一定时间内未能获取到锁,线程可以选择放弃或执行其他操作,避免无限期等待导致死锁。
- 加锁顺序一致:确保所有线程以相同顺序获取多个互斥锁。例如,若线程有时需要同时获取
线程池的设计与实现
- 线程池数据结构设计
- 任务队列:使用链表或数组来实现任务队列。链表实现更灵活,可动态添加和删除任务。例如,定义任务结构体:
typedef struct Task {
void (*func)(void*);
void *arg;
struct Task *next;
} Task;
- 线程数组:定义一个数组来存放线程
pthread_t *threads;
,并记录线程池中的线程数量int thread_count;
。 - 同步机制:包含上述提到的用于保护任务队列的互斥锁
pthread_mutex_t task_mutex;
和条件变量pthread_cond_t task_cond;
,以及记录任务队列当前任务数量的变量int task_count;
。
- 线程池初始化
- 初始化互斥锁和条件变量:
pthread_mutex_init(&task_mutex, NULL);
pthread_cond_init(&task_cond, NULL);
- 创建线程:
threads = (pthread_t *)malloc(thread_count * sizeof(pthread_t));
for (int i = 0; i < thread_count; i++) {
pthread_create(&threads[i], NULL, worker_thread, NULL);
}
- 其中
worker_thread
是工作线程的执行函数。
- 工作线程函数
void *worker_thread(void *arg) {
while (1) {
Task *task;
pthread_mutex_lock(&task_mutex);
while (task_count == 0) {
pthread_cond_wait(&task_cond, &task_mutex);
}
task = get_task_from_queue();
task_count--;
pthread_mutex_unlock(&task_mutex);
if (task) {
task->func(task->arg);
free(task);
}
}
return NULL;
}
- 添加任务到线程池
void add_task_to_pool(void (*func)(void*), void *arg) {
Task *new_task = (Task *)malloc(sizeof(Task));
new_task->func = func;
new_task->arg = arg;
new_task->next = NULL;
pthread_mutex_lock(&task_mutex);
add_task_to_queue(new_task);
task_count++;
pthread_cond_signal(&task_cond);
pthread_mutex_unlock(&task_mutex);
}
- 线程池销毁
- 首先设置一个标志表示线程池要销毁,例如
int pool_destroy = 1;
。 - 然后向任务队列中添加特殊任务(如
NULL
任务),以通知工作线程退出。 - 等待所有线程结束:
- 首先设置一个标志表示线程池要销毁,例如
for (int i = 0; i < thread_count; i++) {
pthread_join(threads[i], NULL);
}
- 最后释放相关资源,如销毁互斥锁、条件变量,释放任务队列和线程数组的内存等。
通过以上设计,可有效避免线程竞争和死锁,实现高效的多线程并发处理,在线程池的帮助下,能更合理地管理线程资源,提高基于C语言的Linux网络应用的整体性能。