信号量
信号量的取值可以为0(表示没有保存下来的唤醒操作),可以为正值(表示有一个或者多个唤醒操作)。
两个操作:down操作:若该值大于0,将其值减1(即用掉一个保存的唤醒信号);若该值为0,则进程将睡眠,并且down操作并未结束!检查数值,修改变量值以及可能发生的睡眠操作均作为一个单一的,不可分割的原子操作完成。保证一旦一个信号量操作开始,则在该操作完成或者阻塞之前,其他进程均不允许访问该信号量。所谓原子操作,是指一组相关联的操作要么都不间断地执行,要么都不执行
Dijkstra原来的论文中,他分别使用名称是P和V而不是down和up,而在操作系统概念一书中,使用wait(),signal()
信号量的实例:
用信号量解决生产者-消费者问题
1 #define N 100 /*缓冲区中槽的数目*/ 2 typedef int semaphore; /*信号量是一种特殊的整形数据*/ 3 semaphore mutex = 1; /*控制对临界区的访问*/ 4 semaphore empty = N; /*计数缓冲区的空槽数目*/ 5 semaphore full = 0; /*计数缓冲区的满槽数目*/ 6 7 8 void producer(void) 9 { 10 int item; 11 12 while(true){ /*TRUE是常量1*/ 13 item = produce_item(); /*产生放在缓冲区中的一些数据*/ 14 down(&empty); /*将空槽数目减1*/ 15 down(&mutex); /*进入临界区*/ 16 insert_item(item); /*将新数据项放到缓冲区中*/ 17 up(&mutex); /*离开临界区*/ 18 up(&full); /*将满槽的数目加1*/ 19 } 20 } 21 22 void consumer(void) 23 { 24 int item; 25 26 while(true){ /*无限循环*/ 27 down(&full); /*将满槽数目减1*/ 28 down(&mutex); /*进入临界区*/ 29 item = remove_item(); /*从缓冲区中取出数据项*/ 30 up(&mutex); /*离开临界区*/ 31 up(&empty); /*将空槽数目加1*/ 32 consume_item(item); /*处理数据项*/ 33 } 34 }
该方案使用了三个信号量:full,用来记录充满缓冲槽的数目,初值为0;empty,记录空的缓冲槽数目,初值为N;mutex,用来确保生产者和消费者不会同时访问缓冲区,初值为1,保证同时只有一个进程可以进入临界区。(如果每个进程在进入临界区前执行一个down操作,并在刚刚退出时执行一个up操作,就能实现互斥)
该方案中,实际上是通过两种不同的方式来使用信号量,信号量mutex用于互斥,保证任一时刻只有一个进程读写缓冲区和相关变量。而信号量full和empty用来保证某种事件的顺序发生或者不发生,用于实现同步。