并发
条件变量
info
example
需要条件变量的原因,场景:父线程需要在子线程执行完毕后继续执行。父线程调用wait()
就会一直卡在这,直到其他线程调用signal()
。函数参数:wait(condition, mutex)
,signal(condition)
。
伪代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int done = 0; // 用于表明子线程是否运行完毕
mutex m;
condition c;
void child() {
lock(m);
done = 1;
signal(c);
unlock(m);
}
void father() {
create_new_thread(child);
lock(m);
while (done == 0)
wait(c, m);
unlock(m);
}
wait
调用时,锁是拿到了的,没拿到的话也进不来。调用wait
之后释放锁,同时让线程处于等待状态,直到其他线程调用signal
,将其唤醒,同时锁又被拿到了。
在这种写法下,无论一开始锁是被谁拿到的,都会以我们预期的方式运行下去。
- 为什么,
wait
和signal
需要lock
?- 为什么需要
while
?
如果没有done
会出现什么问题呢?假如子线程先执行,执行到signal
,但是目前没有哪一个线程在c
这个条件变量上等待,执行完child
之后,father
的wait
会一直卡在那。
如果没有lock
会出先什么问题呢?假如父线程先执行,判断done
为0
之后没有执行wait
而是直接执行了child
,执行到signal
时,还是没有哪一个线程在c
这个条件变量上等待,最终wait
会一直卡在那。
生产者消费者模型
伪代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22mutex m;
condition c;
void producer() {
for (int i = 0; i < loops; i++) {
lock(m);
if (buffer is full)
wait(c, m);
put(i);
signal(c);
unlock(m);
}
}
void consumer() {
for (int i = 0; i < loops; i++) {
lock(m);
if (buffer is empty)
wait(c, m);
get();
signal(c);
unlock(m);
}
}
问题:c1
先运行,发现buffer
中为空,所以需要wait
,执行p1
,buffer
中不满,因此put
,同时发出signal
,这时c1
的wait
结束,‘可以’往下执行,锁目前还是在p1
那里,p1
一直运行,直到发现buffer
满了,这时p1
将锁释放,消费者们可以运行了。但不巧的是,c2
开始运行,将buffer
中消耗完,这时c1
继续执行但是get
的时候就会发生问题。
解决方法:将if
都改成while
,这样当c1
执行时还需要判断buffer
中是否为空。
问题:假如buffer
中只能放一个元素,c1
和c2
先运行,发现buffer
中为空,所以都在wait
,p1
开始运行,向buffer
中put
一个元素,可以唤醒c1
和c2
,假设唤醒的是c1
,这个时候锁还是在p1
,因为buffer
满了,所以p1
也开始wait
,c1
执行,wait
被唤醒同时拿到了锁,get
,假设唤醒c2
同时wait
,但是因为buffer
中没有元素,若c2
开始执行也wait
了,最终结果就是三个都wait
。
假如
buffer
中可以放很多的元素,那么p1
在unlock
以后会执行什么呢?race
?经测试确实是要race。
唤醒只会唤醒一个。
解决办法:出现这种情况的原因是,消费者会唤醒消费者,当然如果有多个生产者的话,也会出现生产者唤醒生产者的问题,因此要做到生产者只能唤醒消费者,消费者只能唤醒生产者。需要两个条件变量。
伪代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20mutex m;
condition fill, empty;
void producer() {
while (1) {
lock(m);
if (buffer is fill)
wait(empty, m); // 生产者等空
signal(fill); // 说满了,消费者可以干活了
unlock(m);
}
}
void consumer() {
while (1) {
lock(m);
if (buffer is empty)
wait(fill, m); // 消费者等满
signal(empty);
unlock(m);
}
}
signal
和broadcast
区别,例:生产者释放内存,消费者申请内存,假如c1
和c2
分别申请100
和10
的内存,因为目前没有空闲内存,所以两个线程wait
,p1
释放了50
的内存,但是唤醒的是c1
,由于50 < 100
,所以c1
也开始wait
,最终导致三个线程都wait
。
那么,while的版本改成broadcast的话还会出现那种问题吗?即还需要两个条件变量吗?
c1,c2先执行,由于buffer空,都wait,p1执行,put,broadcast,c1和c2都被唤醒,p1解锁。目前的情况是,三个线程都没有锁。