信号量、条件变量与同步

信号量、条件变量与同步

2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

信号量 v.s. 条件变量

信号量

  • 互斥锁的自然推广
  • 干净、优雅:没有条件变量的 “自旋”

条件变量

  • 万能:适用于任何同步条件
  • 不太好用:代码总感觉不太干净
synchronized (obj) {  // reads better in Java
    while (!cond) {
        obj.wait();
    }
    ...
}
2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

面对更复杂的同步条件

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

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

center

2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

尝试

条件变量

  • 同步条件:avail[lhs] && avail[rhs]
  • 背模板即可
    • (期末考试通关密码)

信号量

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

成功的尝试:信号量

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

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

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

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

Workaround 2: Lock Ordering

  • 给叉子编号,总是先拿编号小的
2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

Caveat

信号量不总是 “优雅”

  • 数值型资源不总是能很好地代表同步条件

2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

用条件变量实现信号量

P 操作的同步条件

  • sem->count > 0
void P(sem_t *sem) {
    hold(&sem->mutex) {
        while (!COND)
            cond_wait(&sem->cv, &sem->mutex);
        sem->count--;
    }
}

void V(sem_t *sem) {
    hold(&sem->mutex) {
        sem->count++;
        cond_broadcast(&sem->cv);
    }
}
2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

用信号量实现条件变量

void wait(struct condvar *cv, mutex_t *mutex) {
    mutex_lock(&cv->lock);
    cv->nwait++;
    mutex_unlock(&cv->lock);

    mutex_unlock(mutex);
    P(&cv->sleep);

    mutex_lock(mutex);
}

void broadcast(struct condvar *cv) {
    mutex_lock(&cv->lock);

    for (int i = 0; i < cv->nwait; i++) {
        V(&cv->sleep);
    }
    cv->nwait = 0;

    mutex_unlock(&cv->lock);
}
2024 南京大学《操作系统:设计与实现》
信号量、条件变量与同步

实现困难的本质原因

先释放锁,再执行 P

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

先执行 P,再释放锁

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

那怎么办 😂

2024 南京大学《操作系统:设计与实现》