记录型信号量
在整型信号量机制中的wait操作,只要是信号量S≤0,就会不断地测试。因此,该机制并未遵循“让权等待”的准则,而是使进程处于“忙等”的状态。记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。但在采取了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。为此,在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程。
1. struct semaphore 2. { 3. int value; //记录资源个数 4. PCB *queue; //记录等待在该信号量上的进程 5. } 6. P(semaphore s); //消费资源 7. V(semaphore s); //产生资源
1. P(semaphore s) 2. { 3. s.value--; 4. if(s.value < 0) { 5. sleep(s.queue); 6. } 7. }
1. V(semaphore s) 2. { 3. s.value++; 4. if(s.value <= 0) { 5. wakeup(s.queue); 6. } 7. }
AND型信号量
前面所述的进程互斥问题针对的是多个并发进程仅共享一个临界资源的情况。在有些应用场合,是一个进程往往需要获得两个或更多的共享资源后方能执行其任务。假定现有两个进程A和B,它们都要求访问共享数据D和E,当然,共享数据都应作为临界资源。
AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其他所有可能为之分配的资源也不分配给它。也就是,对若干个临界资源的分配采取原子操作方式:要么把它所请求的资源全部分配到进程,要么一个也不分配。
信号量集
在前面所述的记录型信号量机制中,wait(S)或signal(S)操作仅能对信号量施以加1或减1操作,意味着每次只能对某类临界资源进行一个单位的申请或释放。当一次需要N个单位时,便要进行N次wait(S)操作,这显然是低效的,甚至会增加死锁的概率。此外,在有些情况下,为确保系统的安全性,当所申请的资源数量低于某一下限值时,还必须进行管制,不予以分配。因此,当进程申请某类临界资源时,在每次分配之前,都必须测试资源的数量,判断是否大于可分配的下限值,决定是否予以分配。
信号量的应用
利用信号量实现进程互斥
为使多个进程能互斥地访问某临界资源,只需为该资源设置一互斥信号量mutex,并设其初始值为1,然后将各进程访问该资源的临界区CS置于wait(mutex)和signal(mutex)操作之间即可。
利用信号量实现前趋关系
可利用信号量来描述程序或语句之间的前趋关系。设有两个并发执行的进程P1和P2。P1中有语
句S1;P2中有语句S2。我们希望在S1执行后再执行S2。为实现这种前趋关系,只需使进程P1和P2共享一个公用信号量S,并赋予其初值为0,将signal(S)操作放在语句S1后面,而在S2语句前面插入wait(S)操作,即
在进程P1中,用S1;signal(S);
在进程P2中,用wait(S);S2;
由于S被初始化为0,这样,若P2先执行必定阻塞,只有在进程P1执行完S1; signal(S);操作后使S增为1时,P2进程方能成功执行语句S2。同样,我们可以利用信号量按照语句间的前趋关系,写出一个更为复杂的可并发执行的程序。
经典同步问题
生产者消费者问题
生产者消费者问题即一组生产者向一组消费者提供产品,他们共享同一个缓冲区。其中生产者需要在缓冲区有空闲的情况下传输产品;消费者需要在缓冲区有产品的情况下消费产品,且多个生成者或消费者需要互斥使用缓冲区。
针对上述问题,我们需要设置两种同步信号量:empty 和 full,其中empty表示缓冲区空闲的数量,初始值为1,full表示缓冲区存在产品的数量,初始值为0;设置一个互斥量 mutex,初始值为1:
首先确定临界资源为缓冲区
生产者需要缓冲区有空闲才能将生产的产品传入缓冲区,因此设置一个同步信号量empty = N(N为缓冲区可容纳的产品数量)
消费者需要缓冲区有产品才能进行消费,因此需设置一个同步信号量full = 0(一开始没有产品)
为控制生产者消费者对临界资源互斥访问,需要设置一个互斥信号量 mutex = 1
1. //生产者消费者问题 2. semphore full = 0; 3. semphore empty = 1; 4. semphore mutex = 1; 5. producer() 6. { 7. while(1) 8. { 9. //生产产品 10. P(empty);//申请空闲区 11. P(mutex);//申请缓冲区 12. 向缓冲区运送商品 13. V(mutex);//释放缓冲区 14. V(full);//增加缓冲区产品数量 15. } 16. } 17. consumer() 18. { 19. while(1) 20. { 21. P(full);//等待缓冲区的产品 22. P(mutex); 23. 消费 24. V(mutex); 25. V(empty);//消费完,缓冲区的产品减少,增加空闲区 26. } 27. }
P(full)/P(empty)和P(mutex)不可颠倒,否则会发生死锁。
读者-写者问题
存在一个共享数据区,有一些只能对数据区进行读取的进程,一些只能对数据区进行写的进程。
要求:
- 任意读者可以同时读取文件
- 一次只能有一个写者对数据区进行写
- 如果写者正在操作,禁止任何进行对数据区进行操作
1、读者优先,即读者进行操作时,写者不能进行写
对于该问题需要设置几个信号量。
首先由于写者要等所有读者操作完后才能操作,所以需要设置一个表示当前正在操作的读者数量的信号量,readcount,初值为0。
由于读者操作时会修改readcount的值,因此需要设置一个互斥信号量rmutex,初值为1。(控制多个读者互斥使用readcount)
设置一个互斥型号量mutex,用于对写者的数据区进行互斥访问。
1. //读者优先 2. semphore rmutex = 1; 3. semphore mutex = 1; 4. int readcount = 0; 5. reader() 6. { 7. while(1) 8. { 9. P(rmutex);// 10. if(readcount == 0)//如果当前没有读者,则申请读者进入临界区 11. { 12. P(mutex); 13. } 14. readcount++;//有新的读者,修改readcount的值 15. V(rmutex);//readcount修改完,释放对readcount的控制权 16. 读操作 17. P(rmutex);//读者操作结束,需减少readcount的值 18. readcount--; 19. if(readcount == 0)//没有读者则允许写者进入临界区 20. { 21. V(mutex) 22. }; 23. V(rmutex); 24. } 25. } 26. writer() 27. { 28. while(1) 29. { 30. P(mutex);//等待进入临界区 31. 数据写 32. V(mutex); 33. } 34. }
2、公平情况算法
当读者申请访问时,若前面存在写者正在写或正在等待,读者需等待写者完成操作。
为解决此问题,在1的基础上还需要设置一个信号量wmutex,表示是否存在正在写或等待的写者,初值为1
1. semphore rmutex = 1; 2. semphore wmutex = 1; 3. semphore mutex = 1; 4. int readcount = 0; 5. reader() 6. { 7. while(1) 8. { 9. P(wmutex);//申请读 10. P(rmutex);//申请修改readcount 11. if(readcount == 0) 12. { 13. P(mutex); 14. } 15. readcount++; 16. V(rmutex); 17. V(wmutex); 18. //读操作 19. P(rmutex); 20. readcount--; 21. if(readcount == 0) 22. { 23. V(mutex); 24. } 25. v(rmutex); 26. }
3、写者优先
写者优先要求:只要等待队列中存在写者,不管何时到达,都优先于读者被唤醒
此时需设置一个互斥信号量readable 用于表示当前是否有写者,控制写者优先于读者,初值为1
设置两个变量,readcount和writecount,分别用于表示当前读写者的数量,初值为0
设置两个信号量,rmutex用于读者互斥,wmutex用于写者互斥(需要修改readcount和writecount的值)
1. semphore mutex = 1; 2. semphore wmutex = 1; 3. semphore rmutex = 1; 4. semphore readable = 1; 5. int readcount = 0; 6. int writecount = 0; 7. 8. reader() 9. { 10. P(reachable); 11. P(rmutex); 12. if(readcount == 0) 13. { 14. P(mutex); 15. } 16. readcount++; 17. V(rmutex); 18. V(readable); 19. //读操作 20. P(rmutex); 21. readcount--; 22. if(readcount == 0 ) 23. { 24. V(mutex); 25. } 26. V(rmutex); 27. } 28. write() 29. { 30. P(wmutex); 31. if(writecount == 0) 32. { 33. P(readable); 34. } 35. writecount++; 36. V(wmutex); 37. P(mutex); 38. //写操作 39. V(mutex); 40. P(wmutex); 41. writecount--; 42. if(writecount == 0) 43. { 44. V(eadable); 45. } 46. V(wmutex); 47. }
哲学家就餐问题
有5个哲学家在圆桌上就餐,每两个人之间有一只筷子。当一个人进餐时,他需同时拿起左右的两只筷子。
在此问题中,筷子是临界资源,不能同时被两个哲学家拿。
对哲学家和筷子进行编号,0-4。当哲学家同时拿起右边的筷子会发生死锁。
1. semphore chopstick[5] = {1, 1, 1, 1, 1}; 2. philosphere(int i) 3. { 4. if(i&1)//奇数号哲学家 5. { 6. P(chopstick[i%5]); 7. P(chopstick[(i+1)%5]); 8. //进餐 9. V(chopstick[i%5]); 10. V(chopstick[(i+1)%5]); 11. } 12. else 13. { 14. P(chopstick[(i+1)%5]); 15. P(chopstick[i%5]); 16. //进餐 17. V(chopstick[(i+1)%5]); 18. V(chopstick[i%5]); 19. } 20. }
解决:规定奇数号哲学家先拿起左边的筷子后再拿起右边的筷子,规定偶数号哲学家先拿起右边的筷子再拿起左边的筷子。