Linux条件变量:pthread_cond_init、pthread_cond_wait等函数详解
1. 条件变量概述
条件变量(Condition Variable)是线程同步的重要机制,用于线程间的等待和通知。它与互斥锁配合使用,可以高效地实现线程的阻塞和唤醒。
1.1 条件变量的作用
线程等待:当条件不满足时,线程可以主动等待
条件通知:当条件满足时,通知等待的线程继续执行
避免忙等待:减少CPU资源的浪费
1.2 条件变量与互斥锁的关系
条件变量必须与互斥锁一起使用,典型的使用模式:
获取互斥锁
检查条件,不满足则等待
条件满足后执行操作
释放互斥锁
2. 条件变量函数详解
2.1 函数概览表
函数名 | 功能描述 | 头文件 |
---|---|---|
pthread_cond_init | 初始化条件变量 | #include <pthread.h> |
pthread_cond_wait | 等待条件变量 | #include <pthread.h> |
pthread_cond_signal | 唤醒一个等待线程 | #include <pthread.h> |
pthread_cond_broadcast | 唤醒所有等待线程 | #include <pthread.h> |
pthread_cond_destroy | 销毁条件变量 | #include <pthread.h> |
2.2 函数详细说明
2.2.1 pthread_cond_init - 初始化条件变量
功能:初始化条件变量,可以指定属性或使用默认值。
参数说明:
参数名 | 类型 | 含义 | 示例值 | 示例含义 |
---|---|---|---|---|
cond | pthread_cond_t* | 指向条件变量的指针 | &cond | 条件变量地址 |
attr | const pthread_condattr_t* | 条件变量属性 | NULL | 默认属性 |
返回值:成功返回0,失败返回错误码。
使用示例:
pthread_cond_t cond;
if (pthread_cond_init(&cond, NULL) != 0) {perror("条件变量初始化失败");exit(EXIT_FAILURE);
}
2.2.2 pthread_cond_wait - 等待条件变量
功能:等待条件变量,自动释放互斥锁并进入等待状态。
参数说明:
参数名 | 类型 | 含义 | 示例值 | 示例含义 |
---|---|---|---|---|
cond | pthread_cond_t* | 条件变量指针 | &cond | 条件变量地址 |
mutex | pthread_mutex_t* | 互斥锁指针 | &mutex | 互斥锁地址 |
返回值:成功返回0,失败返回错误码。
使用示例:
pthread_mutex_lock(&mutex);
while (condition == 0) { // 必须用while循环检查条件pthread_cond_wait(&cond, &mutex);
}
// 条件满足后的操作
pthread_mutex_unlock(&mutex);
2.2.3 pthread_cond_signal - 唤醒单个线程
功能:唤醒一个在该条件变量上等待的线程。
参数说明:
参数名 | 类型 | 含义 | 示例值 | 示例含义 |
---|---|---|---|---|
cond | pthread_cond_t* | 条件变量指针 | &cond | 条件变量地址 |
返回值:成功返回0,失败返回错误码。
使用示例:
pthread_mutex_lock(&mutex);
condition = 1; // 改变条件
pthread_cond_signal(&cond); // 唤醒一个等待线程
pthread_mutex_unlock(&mutex);
2.2.4 pthread_cond_broadcast - 唤醒所有线程
功能:唤醒所有在该条件变量上等待的线程。
参数说明:
参数名 | 类型 | 含义 | 示例值 | 示例含义 |
---|---|---|---|---|
cond | pthread_cond_t* | 条件变量指针 | &cond | 条件变量地址 |
返回值:成功返回0,失败返回错误码。
使用示例:
pthread_mutex_lock(&mutex);
condition = 1; // 改变条件
pthread_cond_broadcast(&cond); // 唤醒所有等待线程
pthread_mutex_unlock(&mutex);
2.2.5 pthread_cond_destroy - 销毁条件变量
功能:销毁条件变量,释放相关资源。
参数说明:
参数名 | 类型 | 含义 | 示例值 | 示例含义 |
---|---|---|---|---|
cond | pthread_cond_t* | 条件变量指针 | &cond | 条件变量地址 |
返回值:成功返回0,失败返回错误码。
3. 条件变量实战应用
3.1 基础示例:生产者-消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;
int in = 0, out = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_producer = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_consumer = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {int item;for (int i = 0; i < 10; i++) {item = i;pthread_mutex_lock(&mutex);while (count == BUFFER_SIZE) {pthread_cond_wait(&cond_producer, &mutex);}buffer[in] = item;in = (in + 1) % BUFFER_SIZE;count++;printf("Producer: item %d produced, count: %d\n", item, count);pthread_cond_signal(&cond_consumer);pthread_mutex_unlock(&mutex);usleep(100000);}return NULL;
}
void* consumer(void* arg) {int item;for (int i = 0; i < 10; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);}item = buffer[out];out = (out + 1) % BUFFER_SIZE;count--;printf("Consumer: item %d consumed, count: %d\n", item, count);pthread_cond_signal(&cond_producer);pthread_mutex_unlock(&mutex);usleep(150000);}return NULL;
}
int main() {pthread_t prod_t, cons_t;pthread_create(&prod_t, NULL, producer, NULL);pthread_create(&cons_t, NULL, consumer, NULL);pthread_join(prod_t, NULL);pthread_join(cons_t, NULL);pthread_cond_destroy(&cond_producer);pthread_cond_destroy(&cond_consumer);pthread_mutex_destroy(&mutex);return 0;
}
3.2 实用示例:线程池任务调度
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define THREAD_NUM 3
#define TASK_QUEUE_SIZE 10
typedef struct {int task_id;void (*func)(int);
} Task;
Task task_queue[TASK_QUEUE_SIZE];
int task_count = 0;
int task_front = 0, task_rear = 0;
pthread_mutex_t task_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t task_cond = PTHREAD_COND_INITIALIZER;
int stop_flag = 0;
void print_task(int id) {printf("Task %d is processing by thread %lu\n", id, pthread_self());sleep(1);
}
void add_task(int task_id) {pthread_mutex_lock(&task_mutex);if (task_count < TASK_QUEUE_SIZE) {task_queue[task_rear].task_id = task_id;task_queue[task_rear].func = print_task;task_rear = (task_rear + 1) % TASK_QUEUE_SIZE;task_count++;pthread_cond_signal(&task_cond);printf("Task %d added to queue\n", task_id);}pthread_mutex_unlock(&task_mutex);
}
void* worker_thread(void* arg) {while (1) {pthread_mutex_lock(&task_mutex);while (task_count == 0 && !stop_flag) {pthread_cond_wait(&task_cond, &task_mutex);}if (stop_flag && task_count == 0) {pthread_mutex_unlock(&task_mutex);break;}Task task = task_queue[task_front];task_front = (task_front + 1) % TASK_QUEUE_SIZE;task_count--;pthread_mutex_unlock(&task_mutex);task.func(task.task_id);}return NULL;
}
int main() {pthread_t threads[THREAD_NUM];for (int i = 0; i < THREAD_NUM; i++) {pthread_create(&threads[i], NULL, worker_thread, NULL);}for (int i = 0; i < 15; i++) {add_task(i);usleep(500000);}sleep(5);pthread_mutex_lock(&task_mutex);stop_flag = 1;pthread_cond_broadcast(&task_cond);pthread_mutex_unlock(&task_mutex);for (int i = 0; i < THREAD_NUM; i++) {pthread_join(threads[i], NULL);}pthread_cond_destroy(&task_cond);pthread_mutex_destroy(&task_mutex);return 0;
}
4. 条件变量使用要点
4.1 虚假唤醒(Spurious Wakeup)
问题:线程可能在没有收到signal/broadcast的情况下被唤醒
解决方案:始终在while循环中检查条件
while (condition == 0) { // 不要用ifpthread_cond_wait(&cond, &mutex);
}
4.2 条件变量的正确使用模式
// 等待线程
pthread_mutex_lock(&mutex);
while (condition_is_false) {pthread_cond_wait(&cond, &mutex);
}
// 执行操作
pthread_mutex_unlock(&mutex);
// 通知线程
pthread_mutex_lock(&mutex);
condition_is_true = 1;
pthread_cond_signal(&cond); // 或broadcast
pthread_mutex_unlock(&mutex);
5. 面试题精选
5.1 基础概念题
1. 条件变量和互斥锁的区别是什么?
参考答案:
互斥锁用于保护临界区,确保同一时间只有一个线程访问共享资源
条件变量用于线程间通信,让线程在条件不满足时等待,条件满足时被唤醒
条件变量必须与互斥锁配合使用
2. pthread_cond_wait为什么需要互斥锁参数?
参考答案:
保证在检查条件和进入等待之间的原子性
防止丢失唤醒(lost wakeup)问题
自动释放锁并进入等待,被唤醒后自动重新获取锁
5.2 实际应用题
1. 什么是虚假唤醒?如何避免?
参考答案:
虚假唤醒是指线程在没有收到明确信号的情况下从等待状态返回。避免方法:
使用while循环而不是if语句检查条件
每次被唤醒后重新检查条件是否真正满足
2. signal和broadcast的区别?
参考答案:
signal唤醒一个等待线程(如果有多个等待,只唤醒一个)
broadcast唤醒所有等待线程
signal效率更高,broadcast更安全但可能引起"惊群效应"
5.3 高级话题
1. 条件变量超时等待如何实现?
参考答案:
使用pthread_cond_timedwait
函数:
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5; // 5秒超时
int ret = pthread_cond_timedwait(&cond, &mutex, &ts);
if (ret == ETIMEDOUT) {// 超时处理
}
2. 条件变量在性能优化中的注意事项
参考答案:
尽量减少条件变量的使用频率
使用broadcast要谨慎,避免不必要的线程唤醒
条件检查应该尽量简单快速
考虑使用无锁数据结构替代条件变量
6. 常见错误与调试技巧
6.1 常见错误
忘记使用while循环:导致虚假唤醒问题
在锁定互斥锁之前调用signal:可能丢失唤醒
条件变量与错误的互斥锁配对:导致未定义行为
忘记销毁条件变量:资源泄漏
6.2 调试技巧
添加调试输出:跟踪线程的等待和唤醒过程
使用valgrind检查:检测资源泄漏和锁问题
死锁检测:检查锁的获取和释放顺序
7. 总结
条件变量是Linux多线程编程中强大的同步工具,正确使用可以大大提高程序效率。关键要点:
理解基本原理:条件变量用于线程间通信和同步
掌握正确用法:必须与互斥锁配合,使用while循环检查条件
区分signal和broadcast:根据场景选择合适的唤醒方式
注意资源管理:正确初始化和销毁条件变量
通过本文的学习,希望大家能够熟练地在C语言程序中使用条件变量,解决实际的线程同步问题,并应对相关的面试考察。