互斥
并发数据结构
是时候面对真正的并发编程了
理解典型的同步问题
理解实现同步的方法
(你们遇到的)
90% 的实际并发问题都是生产者-消费者 问题。学会你们就赢了。
生产者 (线程) 生产资源 (一个对象),生产时间不确定
消费者 (线程) 消费资源 (取走一个对象),消费时间也不确定
如何协调它们的生产/消费?
void consume() {
object_t *obj = NULL;
while (!obj) { // 队列空时 consumer 在 spin!
lock(&lk);
obj = queue_pop();
unlock(&lk);
}
consume(obj);
}
void produce() {
object_t *obj = produce();
int succ = false;
while (!succ) { // 队列满时无法放入,producer 在 spin!
lock(&lk);
succ = queue_push(obj);
unlock(&lk);
}
}
(更精简的表达) 有两种线程
void producer() {
while (1) printf("("); // push
}
void consumer() {
while (1) printf(")"); // pop
}
在不受并发控制的前提下,任意的括号序列都是合法的
((())())(((
合法(((())))
, (()))
不合法两个或两个以上随时间变化的量在变化过程中保持一定的相对关系
异步 (Asynchronous) = 不同步
在某个时间点共同达到一致的状态 。理解并发程序的方法:把线程想象成我们自己。
我们本身是异步的执行流,但需要在某个点汇合
对于一个 “条件变量” cv 代表了一个同步条件
pthread 条件变量:和互斥锁联合使用
需要
mutex_lock(&mutex);
while (!cond) {
wait(&cv, &mutex);
}
assert(cond); // 互斥锁必须保证退出循环时 cond 依然成立
mutex_unlock(&mutex);
broadcast(&cv);
void produce() {
mutex_lock(&mutex);
while (!(count < n)) wait(&cv, &mutex);
printf("("); count++;
mutex_unlock(&mutex);
broadcast(&cv);
}
void consume() {
mutex_lock(&mutex);
while (!(count > 0)) wait(&cv, &mutex);
printf(")"); count--;
mutex_unlock(&mutex);
broadcast(&cv);
}
mutex 大幅简化了正确性的证明
有三种线程,分别打印 <
, >
, 和 _
<><_
和 ><>_
组合使用条件变量,只要回答三个问题:
<
” 的条件?>
” 的条件?_
” 的条件?
管理员 (操作系统代码) 有一个手环
任何人想进入更衣室都需要通过管理员
完全没有必要限制手环的数量——让更多同学可以进入更衣室
做一点扩展——线程可以任意 “变出” 一个手环
“手环” = “令牌” = “一个资源” = “信号量” (semaphore)
信号量 = 互斥锁 + 条件变量
信号量设计的重点
void producer() {
P(&empty); // P()返回 -> 得到手环
printf("("); // 假设线程安全
V(&fill);
}
void consumer() {
P(&fill);
printf(")");
V(&empty);
}
哲学家 (线程) 有时思考,有时吃饭
void philosopher_thread(int id) {
int lhs = (id - 1 + n) % n, rhs = (id + 1) % n;
mutex_lock(&mutex);
while (!(has_fork[lhs] && has_fork[rhs])) {
wait(&cv, &mutex);
} // 出循环时,循环条件一定为假
has_fork[lhs] = has_fork[rhs] = false;
mutex_unlock(&mutex);
philosopher_eat();
mutex_lock(&mutex);
has_fork[lhs] = has_fork[rhs] = true;
broadcast(&cv); // 对所有等待的人喊:叉子放回去啦,快看看吧!
mutex_unlock(&mutex);
}
“Master/Slave”
void philosopher_thread(int id) {
send_request(id, EAT);
P(allowed[id]); // waiter 会把叉子递给哲学家
philosopher_eat();
send_request(id, DONE);
}
void waiter_thread() {
while (1) {
(id, status) = receive_request();
if (status == EAT) { ... }
if (status == DONE) { ... }
}
}
你可能会觉得,管叉子的人是性能瓶颈
- 一大桌人吃饭,每个人都叫服务员的感觉
本次课内容与目标
Take-away messages