生产者-消费者问题 (和解决)

生产者-消费者问题 (和解决)

2024 南京大学《操作系统:设计与实现》
生产者-消费者问题 (和解决)

经典同步问题:生产者-消费者问题

学废你就赢了

  • 99% 的实际并发问题都可以用生产者-消费者解决

Producer 和 Consumer 共享一个缓冲区

  • Producer (生产数据):如果缓冲区有空位,放入;否则等待
  • Consumer (消费数据):如果缓冲区有数据,取走;否则等待
void produce(Object obj);
Object consume();
2024 南京大学《操作系统:设计与实现》
生产者-消费者问题 (和解决)

生产者-消费者问题的简化

缓冲区太麻烦,我们有一个简化版问题

void produce() { printf("("); }
void consume() { printf(")"); }
  • 生产 = 打印左括号 (push into buffer)
  • 消费 = 打印右括号 (pop from buffer)
  • printf 前后增加代码,使得打印的括号序列满足
    • 一定是某个合法括号序列的前缀
    • 括号嵌套的深度不超过 nn
      • n=3n=3, ((())())((( 合法
      • n=3n=3, (((()))), (())) 不合法
2024 南京大学《操作系统:设计与实现》
生产者-消费者问题 (和解决)

同步:先来先等待

void produce() {
    wait_until(括号深度 < n) {
        printf("(");
    }
}

void consume() {
    wait_until(括号深度 > 0) {
        printf(")");
    }
}

让我们试着实现它

  • 不过,小心并发的危险
2024 南京大学《操作系统:设计与实现》
生产者-消费者问题 (和解决)

能不能不自旋?

我们发明了条件变量!

  • 把条件用一个变量来替代
  • 条件不满足时等待,条件满足时唤醒
mutex_lock(&lk);
if (!condition) {
    cond_wait(&cv, &lk);
}
// Wait for someone for wake-up.
assert(condition);

mutex_unlock(&lk);
cond_signal(&cv);  // Wake up a (random) thread
cond_broadcast(&cv);  // Wake up all threads
2024 南京大学《操作系统:设计与实现》
生产者-消费者问题 (和解决)

Caveat: 小心并发!

条件变量的正确打开方式

  • 使用 while 循环和 broadcast
    • 总是在唤醒后再次检查同步条件
    • 总是唤醒所有潜在可能被唤醒的人
mutex_lock(&mutex);
while (!COND) {
  wait(&cv, &mutex);
}
assert(cond);

...

mutex_unlock(&mutex);
2024 南京大学《操作系统:设计与实现》