几个经典同步问题的思考
生产者与消费者问题:有限缓冲前提;信号量mutex提供了对有限缓冲区互斥访问要求,初始化为0;信号量empty和full分别用来表示当前空缓冲区数量和满缓冲区数量。empty初始化为n,full初始化为0;
生产者代码:
1: do{
2: produce an item in the current loop
3: wait(empty);//查询缓冲区是否还有空缓冲项
4: wait(mutex);//获取缓冲区的使用权
5: add item to buffer
6: signal(mutex);//释放缓冲区的使用权
7: signal(full);//增加满缓冲区数量,通知消费者使用
8: }while(1);
消费者代码:
1: do{
2:
3: wait(full);//查询缓冲区是否还有满缓冲区项
4:
5: wait(mutex)//获取缓冲区的使用权
6:
7: remove an item in the buffer
8:
9: signal(mutex);//释放缓冲区的使用权
10:
11: signal(empty);//增加空缓冲区数量,通知生产者可以生产新产品呢
12:
13: consume an item in the current loop;
14:
15: }while(1);
读者——作者问题:这个问题有两个子问题:1、第一读者——作者问题:要求没有读者需要保持等待除非有一个作者已经获得允许使用共享对象,换句话来说,就是没有读者会因为有一个作者在等待而会等待其他读者的完成。2.第二读者——作者问题:要求一旦作者就绪,那么作者会尽可能快地执行其写操作。换句话来说,如果一个作者等待访问对象,那么不会有新读者开始读操作。接下来,我们来看看两种情况下源代码的实现。
第一读者——作者问题:信号量mutex和wrt初始化为1,wrt被读者作者共享,用于对共享对象的互斥访问控 制要求,整型变量readcount代表当前读者数量,初始化为0,而mutex用于实现对readcount变量的互斥访问要求:
作者代码:
1: write(){
2:
3: wait(wrt);//获取共享对象的使用权
4:
5: writing is performed;
6:
7: signal(wrt);//释放共享对象的使用权
8:
9: }
读者代码:
1: reader(){
2:
3: wait(mutex)//获取readcount对象的使用权
4:
5: readcount++;
6:
7: if(readcout==1)
8:
9: wait(wrt);//如果当前读者为第一位读者,则需要进行申请对共享对象的使用权
10:
11: signal(mutex);//释放readcount对象的使用权。在这里,我们需要注意的是,如果第一个读者没有获取到
12:
13: //共享对象的使用权,那么,根本不会释放readcount的使用权,则也就说明接下来的读者
14:
15: //也就不可能会获取到readcount的使用权呢。
16:
17: reading is performed;
18:
19: wait(mutex);//获取readcount对象的使用权
20:
21: readcount--;
22:
23: if(readcout==0)
24:
25: signal(wrt);//如果当前读者是最后一位读者,则需要释放对共享对象的使用权,这样作者才有机会重新
26:
27: //获得对共享对象的使用权
28:
29: signal(mutex);//释放eadcount对象的使用权
30:
31: }
32:
第二读者——作者问题:由于这个问题比较复杂,要求是:
(1)多个读者可以同时进行读取
(2)写者必须互斥,只允许一个写者写,也不允许读者和写者同时进行
(3)写者优先于读者,如果有写者,不管是已经在写,还是有在等待进行写操作的写者,后续读者都必须等待,唤醒时优先考虑写者。
信号量mutex1,mutex2,mutex3,分别用于互斥进入读操作、互斥readcount读写及互斥writecount读写;信号量ca_reader与ca_writer分别用于互斥读者临界区的进入和互斥写者临界区的进入。整型变量readcount和writecount分别代表已经进入读者临界区的读者数和已经进入写者临界区的写者数,初始化readcount=writecount=0;其余的信号量初始化值均为1;
写者代码:
1: writer(){
2:
3: wait(mutex3);//获取对writecount对象的使用权
4:
5: writecount++;
6:
7: if(writecount==1)
8:
9: wait(ca_reader); //如果当前写者是第一个尝试进入写操作临界区,需要等待最后一次在进行读操作的读
10:
11: //者离开并释放进入写操作临界区的使用权,这样写者才能接下来进入写操作临界区进行
12:
13: //写操作。
14:
15: signal(mutex3);//释放对writecount对象的使用权
16:
17: wait(ca_writer);//获取进入写操作临界区的使用权。这里需要提醒的一点是,运行到这里,事实上,已然
18:
19: //没有任何读者写者获取得到了进入写操作临界区的使用权,否则在红色的地方就已经被阻
20:
21: //塞呢。
22:
23: writing is performed;
24:
25: signal(ca_writer);//释放进入写操作临界区的使用权
26:
27: wait(mutex3);//获取对writecount对象的使用权
28:
29: writecount--;
30:
31: if(writecount==0)
32:
33: signal(ca_reader);//如果当前写者为最后一个(其实应该是唯一一个)进入写操作临界区的写者,那
34:
35: //么,此时应该释放对进入读操作临界区使用权,以使读者有机会进入读操作临界区
36:
37: //进行读操作。
38:
39: signal(mutex3);//释放对writecount对象的使用权
40:
41: }
42:
读者代码:
1: reader(){
2:
3: wait(mutex1);//获取进入读者临界区之前的使用权
4:
5: wait(ca_reader);//获取进入读者临界区允可证
6:
7: wait(mutex2);//获取对readcount对象的使用权
8:
9: readcount++;
10:
11: if(readercount==1)
12:
13: wait(ca_writer);//如果当前进入读者临界区的读者是第一个已经进入读者临界区的读者,那么,此时
14:
15: //分两种情况:1、已经有写者在进入写操作,那么阻塞当前读者,同时由于没有释放进
16:
17: //入读者临界区的权力,后续的读者也无法进入读者临界区呢。2、如果当前没有写者
18:
19: //进入写操作,那么,当前读者就可以马上进行读操作呢,同时阻塞写者进入写临界区。
20:
21: //值得大家注意的是,如果当前
22:
23: //读者不是第一个进入读者临界区,那么说明,此时此刻至少还有一个读者在进行读操作
24:
25: //,那么,当前读者也就可以马上直接进行读操作呢。还有一点需要说明的是,如果已经
26:
27: //进入读者临界区的读者,同时其不是第一个读者的话,事实上已经表明当前读者已经属
28:
29: //于在进行读操作的读者呢,不再属于等待进入读操作的读者呢。理解这点很重要。
30:
31: signal(mutex2);//释放对readcount对象的使用权
32:
33: signal(mutex1);//释放对进入读者临界区之前的使用权
34:
35: reading is performed;
36:
37: wait(mutex2);//获取对readcount对象的使用权
38:
39: readcount--;
40:
41: if(readcount==0)
42:
43: signal(ca_writer);//如果当前读者为最后一个在进行读操作的读者,那么此时应该释放进入写操作临界区
44:
45: //的使用权,以使写者能够进行写操作临界区,进行写操作。
46:
47: signal(mutex2);//释放对readcount对象的使用权
48:
49: }
50:
从第二读者-作者问题代码的注释量上来说,我们就应该清楚,这确实是一道很复杂的同步问题,如果事先没有接触过,很难在短时间写出正确的同步代码。这需要自己多多领悟、理解,才能真正做到对同步问题的游刃有余吧。