操作系统:信号量、互斥量、管程
信号量:
在1965年,Dijkstra提出用一个整型变量来累计唤醒次数,称作信号量。一个信号量的取值可以为0或者为正值。Dijkstra建议对信号量设立两种操作:down(P操作)和up(V操作)。这两种操作均为原子操作,意为在该信号量操作结束或阻塞之前,其余进程均不允许访问该信号量。
down操作检查信号量当前的值是否>0,如果>0,将该值-1。如果=0,则进程将睡眠,并且此时down操作并未结束。up操作对信号量的值+1,如果一个或多个进程在该信号量上睡眠,无法完成先前的down操作的时候,则由系统唤醒其中的一个进程,并对信号量进行未完成的down操作。
用信号量解决生产者-消费者问题
此解决方案使用了三个信号量。一个称为full,初始值为0,用来记录已充满的缓冲槽数目,一个称为empty,初始值为N(缓冲槽的数目),记录空的缓冲槽数目,一个称为mutex,初始值为1,用来确保生产者和消费者不会同时访问缓冲区。其代码如下:
#define N 100 typedef int semaphore; //信号量 semaphore mutex = 1; semaphore full = 0; semaphore empty = N; void producer(){ int item; while(TRUE){ item = produce_item();//生产者生产 down(&empty);//空槽数目减少 down(&mutex);//进入临界区 insert_item(item);//将产品放入缓冲区 up(&mutex);//离开临界区 up(&full);//满槽数目增加 } } void consumer(){ int item; while(TRUE){ down(&full); down(&mutex); item = remove_item();//从缓冲区中取出商品 up(&mutex); up(&empty); consume_item(item);//消费商品 } }
互斥量(锁)
如果不需要信号量的计数功能,我们有时候可以采取信号量的简化版本,称为互斥量(mutex)(也有人称作锁)。互斥量可以处于两种状态:加锁和解锁。实际应用中,我们常使用一个整型量,0表示加锁,其他所有值表示解锁。
当一个线程需要访问临界区时,它调用mutex_lock。如果该互斥量当前是解锁的,此调用成功。反之,该线程阻塞,直到临界区中的其他线程调用mutex_unlock。
管程
通常,我们需要花费大量的精力来使用信号量,不谨慎的设计不仅不能得到应有的结果,还可能会导致死锁。为了更易于编写正确的程序,计算机科学家提出了一种高级同步原语:管程。
一个管程是一个由过程,变量及数据结构等组成的一个集合。进程可在任何需要的时候调用管程中的过程,但它们不能在管程之外声明的过程中直接访问管程内部的数据结构。管程有一个很重要的特性,即任一时刻管程内只能有一个活跃进程。这一特性使管程能有效地完成互斥。