信号量、条件变量与同步

信号量、条件变量与同步

2025 南京大学《操作系统原理》
信号量、条件变量与同步

信号量 v.s. 条件变量

信号量

  • 干净、优雅,完美地解决了生产者-消费者问题
  • 但 “count” 不总是能很好地代表同步条件

条件变量

  • 万能:适用于任何同步条件
  • 丑陋:代码总感里有什么脏东西 (spin loop)
synchronized (obj) {  // reads better in Java
    while (!cond) {
        obj.wait();
    }
    ...
}
2025 南京大学《操作系统原理》
信号量、条件变量与同步

让我们上一点难度

Fish

  • TaT_a 打印 <, TbT_b打印 >, TcT_c 打印 _
  • 保证打印出 <><_><>_ 的序列?

一个小困难

  • 假设刚打印完一条完整的鱼
    • <><_><>_
  • <> 都是可行的
    • 信号量似乎不太好表达 “二选一”
    • 除非手动构建 happens-before
2025 南京大学《操作系统原理》
信号量、条件变量与同步

面对更复杂的同步问题……

♂ 学家吃饭问题 (E. W. Dijkstra, 1960)

  • 哲学家 (线程) 有时思考,有时吃饭
  • 吃饭需要同时得到左手和右手的叉子

center

2025 南京大学《操作系统原理》
信号量、条件变量与同步

尝试

条件变量

  • 同步条件:avail[lhs] && avail[rhs]
  • 背模板即可
    • (期末考试 100% 会考,这就是通关密码)

信号量

  • P(&sem[lhs]) && P(&sem[rhs])
  • 看起来没什么问题?
    • 当互斥锁用就行了
2025 南京大学《操作系统原理》
信号量、条件变量与同步

成功的尝试:信号量

如果 5 个哲学家同时举起左手的叉子……

  • 我们需要禁止这件事发生

Workaround 1: 从桌子上赶走一个人

  • 直观理解:大家先从桌上退出
    • 袋子里有 4 张卡
    • 拿到卡的可以上桌吃饭 (拿叉子)
    • 吃完以后把卡归还到袋子

Workaround 2: Lock Ordering

  • 给叉子编号,总是先拿编号小的
2025 南京大学《操作系统原理》
信号量、条件变量与同步

不!这不 “成功”

信号量不总是 “优雅”

  • Systems 要的是 absolutely correct 的方案
  • 数值型资源不总是能很好地代表同步条件

2025 南京大学《操作系统原理》
信号量、条件变量与同步

用信号量实现条件变量 🌶️

Implementing condition variables out of a simple primitive like semaphores is surprisingly tricky. (from a 2003 report)

void wait(cond_t *cv, mutex_t *mutex) {
    atomic_inc(&cv->nwait);
    mutex_unlock(mutex);
    P(&cv->sleep);
    mutex_lock(mutex);
}

void broadcast(cond_t *cv) {
    mutex_lock(&cv->lock);
    for (int i = 0; i < cv->nwait; i++)
        V(&cv->sleep);
    cv->nwait = 0;
    mutex_unlock(&cv->lock);
}
  • 唤醒丢失: 一个 “早就 wait 但没有 P” 的线程会抢走唤醒
2025 南京大学《操作系统原理》
信号量、条件变量与同步

实现困难的本质原因 🌶️

先释放锁,再执行 P

  • 释放锁的一瞬间可能与 broadcast 并发

先执行 P,再释放锁

  • P(&cv->sleep) 会 “永久睡眠”

那怎么办 😂

  • Release-wait 必须实现成 “不可分割的原子操作”
    • 解决不了,问操作系统吧 (实际实现靠得是 futex)
  • 信号量:在合适的时候好用;但不总是好用
2025 南京大学《操作系统原理》