复习
本次课回答的问题
本次课主要内容
两个或两个以上随时间变化的量在变化过程中保持一定的相对关系
异步 (Asynchronous) = 不同步
并发程序的步调很难保持 “完全一致”
再次把线程想象成我们自己
99% 的实际并发问题都可以用生产者-消费者解决 。
void Tproduce() { while (1) printf("("); }
void Tconsume() { while (1) printf(")"); }
在 printf
前后增加代码,使得打印的括号序列满足
((())())(((
合法(((())))
, (()))
不合法为什么叫 “生产者-消费者” 而不是 “括号问题”?
能否用互斥锁实现括号问题?
把 pc.c 中的自旋变成睡眠
条件变量 API
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);
}
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
// 注意回收分配的资源
}
有三种线程,分别打印 <
, >
, 和 _
<><_
和 ><>_
组合使用条件变量,只要回答三个问题:
<
” 的条件?>
” 的条件?_
” 的条件?
操作系统 = 更衣室管理员
*lk = 🔒
,系统调用直接返回*lk = ✅
完全没有必要限制手环的数量——让更多同学可以进入更衣室
做一点扩展——线程可以任意 “变出” 一个手环
“手环” = “令牌” = “一个资源” = “信号量” (semaphore)
信号量设计的重点
void producer() {
P(&empty); // P()返回 -> 得到手环
printf("("); // 假设线程安全
V(&fill);
}
void consumer() {
P(&fill);
printf(")");
V(&empty);
}
哲学家 (线程) 有时思考,有时吃饭
失败的尝试
成功的尝试 (万能的方法)
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);
“Leader/follower” - 生产者/消费者
void Tphilosopher(int id) {
send_request(id, EAT);
P(allowed[id]); // waiter 会把叉子递给哲学家
philosopher_eat();
send_request(id, DONE);
}
void Twaiter() {
while (1) {
(id, status) = receive_request();
if (status == EAT) { ... }
if (status == DONE) { ... }
}
}
你可能会觉得,管叉子的人是性能瓶颈
本次课回答的问题
Take-away message