OSTEP: Concurrency
2022-09-21 13:23:06
本文总阅读量

并发

条件变量

info

  1. pthread_cond_wait()

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
18
int 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,将其唤醒,同时锁又被拿到了。

在这种写法下,无论一开始锁是被谁拿到的,都会以我们预期的方式运行下去。

  1. 为什么,waitsignal需要lock
  2. 为什么需要while

如果没有done会出现什么问题呢?假如子线程先执行,执行到signal,但是目前没有哪一个线程在c这个条件变量上等待,执行完child之后,fatherwait会一直卡在那。

如果没有lock会出先什么问题呢?假如父线程先执行,判断done0之后没有执行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
22
mutex 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,执行p1buffer中不满,因此put,同时发出signal,这时c1wait结束,‘可以’往下执行,锁目前还是在p1那里,p1一直运行,直到发现buffer满了,这时p1将锁释放,消费者们可以运行了。但不巧的是,c2开始运行,将buffer中消耗完,这时c1继续执行但是get的时候就会发生问题。

解决方法:将if都改成while,这样当c1执行时还需要判断buffer中是否为空。

问题:假如buffer中只能放一个元素,c1c2先运行,发现buffer中为空,所以都在waitp1开始运行,向bufferput一个元素,可以唤醒c1c2,假设唤醒的是c1,这个时候锁还是在p1,因为buffer满了,所以p1也开始waitc1执行,wait被唤醒同时拿到了锁,get,假设唤醒c2同时wait,但是因为buffer中没有元素,若c2开始执行也wait了,最终结果就是三个都wait

假如buffer中可以放很多的元素,那么p1unlock以后会执行什么呢?race

经测试确实是要race。

唤醒只会唤醒一个。

解决办法:出现这种情况的原因是,消费者会唤醒消费者,当然如果有多个生产者的话,也会出现生产者唤醒生产者的问题,因此要做到生产者只能唤醒消费者,消费者只能唤醒生产者。需要两个条件变量。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mutex 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);
}
}

signalbroadcast区别,例:生产者释放内存,消费者申请内存,假如c1c2分别申请10010的内存,因为目前没有空闲内存,所以两个线程waitp1释放了50的内存,但是唤醒的是c1,由于50 < 100,所以c1也开始wait,最终导致三个线程都wait

那么,while的版本改成broadcast的话还会出现那种问题吗?即还需要两个条件变量吗?

c1,c2先执行,由于buffer空,都wait,p1执行,put,broadcast,c1和c2都被唤醒,p1解锁。目前的情况是,三个线程都没有锁。

参考

深入解析条件变量(condition variables)

nju-os-2022

Producer-Consumer solution using threads in Java

Producer-Consumer Problem With Example in Java

上一页
2022-09-21 13:23:06
下一页