单机生产者-消费者模型关键在于用empty_count和product_count两个原子计数器控制流程:生产者检查empty_count>0后写入并更新下标,消费者检查product_count>0后读取并更新下标,环形缓冲区下标严格模运算,等待与通知绑定计数器变化,临界区仅保护共享变量操作。

单机环境下实现简单生产者-消费者模型,关键不是“加锁就行”,而是用流程控制逻辑把“谁该动、什么时候动、动完通知谁”这三件事理清楚。缓冲区本身只是个容器,真正起协调作用的是围绕它的状态判断和动作顺序。
用两个计数器驱动流程节奏
缓冲区满或空时,不能靠轮询或强行读写,而应由显式计数器决定是否允许操作:
-
empty_count:记录当前空闲槽位数,初始值 = 缓冲区总容量。生产者每次成功写入前必须检查
empty_count > 0,写入后empty_count--,product_count++ -
product_count:记录已就绪产品数,初始值为 0。消费者每次读取前必须检查
product_count > 0,读取后product_count--,empty_count++ - 这两个变量必须用原子操作或临界区保护(如互斥锁),否则计数错乱会导致越界写或读脏数据
环形缓冲区下标推进要严格按模运算
避免数组越界和覆盖的关键,在于下标更新逻辑不依赖外部状态,只依赖自身位置:
- 生产者写入位置 =
write_index,写完立即更新:write_index = (write_index + 1) % BUFFER_SIZE - 消费者读取位置 =
read_index,读完立即更新:read_index = (read_index + 1) % BUFFER_SIZE - 禁止在未检查计数器的情况下直接修改下标;也禁止用
if (write_index == BUFFER_SIZE) write_index = 0这类分支逻辑——易出错且难维护
等待动作必须与计数器变化绑定
不能让线程空转耗 CPU,也不能跳过状态检查直接操作:
- 生产者发现
empty_count == 0→ 主动让出 CPU(如调用sleep(1)或进入条件等待)→ 不继续执行写入 → 直到被消费者唤醒或重检 - 消费者发现
product_count == 0→ 同样暂停 → 避免读取无效内存或重复消费旧值 - 每次计数器变更后(如
product_count++),应触发一次通知(如pthread_cond_signal或notify()),确保等待方有机会重试
临界区只包裹真正共享的变量操作
锁的粒度要小,只锁“读/写缓冲区数据 + 更新计数器 + 移动下标”这三步,其余逻辑(如生成随机数、打印日志、休眠)全部放在锁外:
- 错误做法:整个 while 循环加锁 → 生产者卡在 sleep 里,消费者永远拿不到锁
- 正确做法:
// 准备数据(锁外)
item = generate_data();
// 进入临界区
lock();
if (empty_count > 0) {
queue[write_index] = item;
write_index = (write_index + 1) % SIZE;
empty_count--;
product_count++;
unlock();
signal_consumer(); // 锁外发通知更安全
} else { unlock(); }











