哲学家吃饭问题 (E. W. Dijkstra, 1960)
哲学家 (线程) 有时思考,有时吃饭
- 吃饭需要同时得到左手和右手的叉子
- 当叉子被其他人占有时,必须等待,如何完成同步?
- 如何用互斥锁/信号量实现?
失败与成功的尝试
失败的尝试
- philosopher.c (如何解决?)
成功的尝试 (万能的方法)
mutex_lock(&mutex);
while (!(avail[lhs] && avail[rhs])) {
wait(&cv, &mutex);
}
avail[lhs] = avail[rhs] = false;
mutex_unlock(&mutex);
mutex_lock(&mutex);
avail[lhs] = avail[rhs] = true;
broadcast(&cv);
mutex_unlock(&mutex);
忘了那些复杂的同步算法吧!
你可能会觉得,管叉子的人是性能瓶颈
- 一大桌人吃饭,每个人都叫服务员的感觉
- Premature optimization is the root of all evil (D. E. Knuth)
抛开 workload 谈优化就是耍流氓
- 吃饭的时间通常远远大于请求服务员的时间
- 如果一个 manager 搞不定,可以分多个 (fast/slow path)
- 把系统设计好,使集中管理不成为瓶颈
- Millions of tiny databases (NSDI'20)
- 把系统设计好,使集中管理不成为瓶颈
Conditional Variables (条件变量, CV)
把 pc.c 中的自旋变成睡眠
- 在完成操作时唤醒
条件变量 API
- wait(cv, mutex)
- 调用时必须保证已经获得 mutex
- 释放 mutex、进入睡眠状态
- signal/notify(cv) 私信:走起
- 如果有线程正在等待 cv,则唤醒其中一个线程
- broadcast/notifyAll(cv) 所有人:走起
- 唤醒全部正在等待 cv 的线程
条件变量:实现生产者-消费者
void Tproduce() {
mutex_lock(&lk);
if (count == n) cond_wait(&cv, &lk);
printf("("); count++; cond_signal(&cv);
mutex_unlock(&lk);
}
void Tconsume() {
mutex_lock(&lk);
if (count == 0) cond_wait(&cv, &lk);
printf(")"); count--; cond_signal(&cv);
mutex_unlock(&lk);
}
- (Small scope hypothesis)
条件变量:实现并行计算
struct job {
void (*run)(void *arg);
void *arg;
}
while (1) {
struct job *job;
mutex_lock(&mutex);
while (! (job = get_job()) ) {
wait(&cv, &mutex);
}
mutex_unlock(&mutex);
job->run(job->arg); // 不需要持有锁
// 可以生成新的 job
// 注意回收分配的资源
}
99% 的实际并发问题都可以用生产者-消费者解决。
void Tproduce() { while (1) printf("("); }
void Tconsume() { while (1) printf(")"); }
在 printf
前后增加代码,使得打印的括号序列满足
- 一定是某个合法括号序列的前缀
- 括号嵌套的深度不超过 n
- n=3,
((())())(((
合法 - n=3,
(((())))
,(()))
不合法
- n=3,
- 同步
- 等到有空位再打印左括号
- 等到能配对时再打印右括号
https://jyywiki.cn/OS/2022/slides/6.slides#/1/3
【并发控制:同步 (条件变量、信号量、生产者-消费者和哲♂学家吃饭问题) [南京大学2022操作系统-P6]】https://www.bilibili.com/video/BV17T4y1S7RS