文章目录
生产者与消费者
- 生产者与消费者问题是多线程编程领域中的一个经典问题,主要用来描述一个或多个生产者线程和一个或多个消费者线程共享有限缓冲区资源时的同步问题
问题
- 问题的核心在于如何保持生产者和消费者之间的协调,确保当缓冲区已满时生产者停止生产,而当缓冲区已空时消费者停止消费,以避免出现资源浪费或死锁的情况
- 生产者:生成数据,放入共享缓冲区
- 消费者:从缓冲区取出数据并处理
- 缓冲区:有限容量的共享资源
挑战
- 互斥访问:防止同时读写造成数据不一致
- 同步协调:
- 缓冲区满时,生产者应等待
- 缓冲区空时,消费者应等待
条件变量
- 条件变量是一种线程同步机制,允许线程在某个条件不满足时阻塞等待,当条件满足时被其他线程唤醒
特性
- 必须与互斥锁配合使用,条件变量本身不提供互斥保护
- 用于线程间的通信与协调
- 解决"忙等待"问题,提高效率
修改条件(谓词)必须在互斥锁保护下
函数接口
初始化
c
#include <pthread.h>
// 静态初始化(全局/静态变量)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 动态初始化(栈/堆变量)
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
等待条件(阻塞)
c
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- 执行过程(原子操作):
- 解锁互斥量 mutex
- 阻塞线程,等待条件变量 cond 的信号
- 收到信号后,重新锁定 mutex
- 返回
发送信号
c
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond); // 唤醒一个等待线程,适合只有一个线程能处理的情况
int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有等待线程,适合多个线程都能处理的情况
接收信号(带超时的等待)
c
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
销毁
c
int pthread_cond_destroy(pthread_cond_t *cond);
- 销毁前确保没有线程在等待,确保所有线程都已退出或不再等待
应用模板
c
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int condition = 0; // 条件变量关联的谓词
// 等待线程
pthread_mutex_lock(&mutex);
// 必须用while循环!
while (!condition) {
pthread_cond_wait(&cond, &mutex);
}
// 执行操作...
pthread_mutex_unlock(&mutex);
// 通知线程
pthread_mutex_lock(&mutex);
condition = 1; // 修改条件
pthread_cond_signal(&cond); // 或 broadcast
pthread_mutex_unlock(&mutex);
示例
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0; // 当前缓冲区数据数量
int in = 0; // 生产者插入位置
int out = 0; // 消费者取出位置
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER; // 非空条件
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER; // 非满条件
// 生产者
void* producer(void* arg) {
int item;
while(1) {
item = rand() % 1000; // 生产数据
pthread_mutex_lock(&mutex);
// 缓冲区满则等待
while(count == BUFFER_SIZE) {
pthread_cond_wait(¬_full, &mutex);
}
// 生产数据
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
count++;
printf("生产者: 生产 %d, 当前数量: %d\n", item, count);
// 通知消费者
pthread_cond_signal(¬_empty);
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟生产耗时
}
return NULL;
}
// 消费者
void* consumer(void* arg) {
int item;
while(1) {
pthread_mutex_lock(&mutex);
// 缓冲区空则等待
while(count == 0) {
pthread_cond_wait(¬_empty, &mutex);
}
// 消费数据
item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count--;
printf("消费者: 消费 %d, 当前数量: %d\n", item, count);
// 通知生产者
pthread_cond_signal(¬_full);
pthread_mutex_unlock(&mutex);
sleep(2); // 模拟消费耗时
}
return NULL;
}
虚假唤醒
- 即使没有线程调用pthread_cond_signal()或pthread_cond_broadcast(),等待在条件变量上的线程也可能被唤醒
- 虚假唤醒的原因:
- 多处理器系统的实现细节
- 信号处理中断
- 条件变量实现的复杂性
解决虚假唤醒
c
// 错误:使用if可能错过检查
pthread_mutex_lock(&mutex);
if (count == 0) {
pthread_cond_wait(&cond, &mutex);
}
// 这里可能count仍然为0!
// 正确:使用while确保条件真正满足
pthread_mutex_lock(&mutex);
while (count == 0) {
pthread_cond_wait(&cond, &mutex);
}
// 这里count一定不为0