面试题答案
一键面试使用Linux C语言条件变量实现线程同步示例
#include <pthread.h>
#include <stdio.h>
// 共享资源
int shared_variable = 0;
// 互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 条件变量
pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
// 线程函数
void* thread_function(void* arg) {
pthread_mutex_lock(&mutex);
// 等待条件满足
while (shared_variable != 1) {
pthread_cond_wait(&cond_var, &mutex);
}
printf("Thread: Shared variable is now 1. Continuing...\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread;
// 创建线程
pthread_create(&thread, NULL, thread_function, NULL);
pthread_mutex_lock(&mutex);
// 模拟一些操作
shared_variable = 1;
// 通知等待的线程
pthread_cond_signal(&cond_var);
pthread_mutex_unlock(&mutex);
// 等待线程结束
pthread_join(thread, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond_var);
return 0;
}
条件变量在高并发场景下的性能瓶颈点
- 上下文切换开销:当线程调用
pthread_cond_wait
时,会释放锁并进入睡眠状态,此时会发生上下文切换。在高并发场景下,大量线程频繁的上下文切换会消耗大量CPU时间。 - 虚假唤醒:即使没有调用
pthread_cond_signal
或pthread_cond_broadcast
,线程也可能被唤醒(虚假唤醒)。这就需要在pthread_cond_wait
返回后再次检查条件是否真正满足,增加了额外的开销。 - 竞争条件:在使用
pthread_cond_signal
时,如果同时有多个线程在等待,可能会出现竞争条件,导致某些线程长时间无法被唤醒。
性能优化方法
- 减少不必要的等待:尽可能准确地判断何时需要等待条件变量,避免不必要的睡眠和上下文切换。例如,在应用逻辑中提前进行一些条件判断,只有在真正需要等待时才调用
pthread_cond_wait
。 - 使用广播与信号的选择:根据场景合理选择
pthread_cond_signal
和pthread_cond_broadcast
。如果只有一个线程需要被唤醒,使用pthread_cond_signal
可以减少竞争和唤醒不必要线程的开销;如果多个线程都可能对条件满足感兴趣,使用pthread_cond_broadcast
。 - 避免虚假唤醒:在
pthread_cond_wait
返回后,始终使用循环检查条件是否真正满足,而不是简单地认为被唤醒条件就一定满足。
while (condition_not_met) {
pthread_cond_wait(&cond_var, &mutex);
}
条件变量比互斥锁单独使用更具优势的业务场景
- 生产者 - 消费者场景
- 示例:假设有一个生产者线程不断生产数据并放入共享缓冲区,消费者线程从共享缓冲区取出数据进行处理。
- 原理分析:如果只使用互斥锁,消费者线程需要不断轮询共享缓冲区是否有数据,这会浪费大量CPU资源。而使用条件变量,消费者线程可以在缓冲区为空时调用
pthread_cond_wait
进入睡眠状态,当生产者向缓冲区放入数据后,调用pthread_cond_signal
唤醒消费者线程。这样可以避免消费者线程无效的轮询,提高系统整体性能。
- 资源等待场景:例如数据库连接池场景,多个线程可能需要获取数据库连接。当连接池中没有可用连接时,线程可以通过条件变量等待,直到有连接被释放回连接池,而不是一直持有互斥锁并不断尝试获取连接。
综上所述,条件变量在需要线程等待某个条件满足的场景下,相比互斥锁单独使用,能更有效地利用系统资源,提高并发性能。