1. 信号量机制的缺陷问题:
在上面的生产者消费者实例中,信号量的工作机制如下(我们以生产者的代码为例):
1 down(&empty); 2 down(&mutex); 3 enter_item(item); 4 up(&mutex); 5 up(&full);
如果交换1号和2号语句,变成:
1 down(&mutex); 2 down(&empty);
那么可能会出现下面的情形: mutex变成0,此时empty == 0,那么生产者阻塞; 此时消费者请求mutex,也阻塞了;
这是一个典型的死锁情形!这也显示了使用信号量机制来进行进程间并发的缺陷,一个小小的错误就会导致巨大的麻烦!
这给程序的编写造成了很大的困难; 为此 Hoare 和 Brinch Hansen 提出了一个更高级别的同步原语---monitor。
2. monitor:
一个monitor就是一个由过程(程序),变量和数据结构的集合,这个集合以模块或包的新式存在。一个用户过程可以调用
一个monitor之中的任何一个过程,但无法直接访问monitor中的数据结构。下面是monitor的形式说明:
1 monitor example 2 integer i; 3 condition c; 4 5 procedure producer(x); 6 ... 7 end; 8 9 procedure consumer(x); 10 ... 11 end; 12 end monitor
Monitor有一个很好的机制来实现进程之间的互斥: 在某个时刻只有一个进程可以在monitor中是活跃的!
Monitors是编程语言范畴的一种结构,所以编译器能够判别monitor中过程的调用和其他过程调用。下图是monitor的运作机制:
Monitor实现进程间互斥的机制是由编译器来实现的,程序员只要知道一点: 只要将各个进程的关键代码段放入monitor中去之后,就不会发生两个进程同时执行关键代码段的情形。
但是当一个进程在monitor中发现自己执行不下去了,那么它该如何阻塞呢?下面引入条件变量机制:
条件变量有两个相关的操作: WAIT 和 SIGNAL。当一个monitor中的程序发现自己不能再执行下去了的时候,它将在某个条件变量上调用WAIT操作。这个操作会使得调用程序阻塞。现在以前不能进入monitor的程序都可以进来了。当一个monitor中的过程执行完毕时,就会对条件变量做一个SIGNAL让其伙伴程序被唤醒。但是为了防止调用了SIGNAL之后不会出现两个过程同时在monitor中被执行的情形,Hoare 和 Brinch Hansen 分别采用了不同的机制:
- Hoare 让新进被唤醒的程序运行,而将另外一个阻塞;
- Brinch Hansen 用了这样一个规定: 调用了SIGNAL的过程必须立刻推出monitor(也就是说:这个SIGNAL操作必须作为这个monitor过程的最后一条语句出现)。
下面是使用monitor机制实现的生产者--消费者过程的代码:
1 monitor ProducerConsumer 2 condition full,empty; 3 integer count; 4 5 procedure enter: 6 begin: 7 if count = N then wait(full); 8 enter_item; 9 count := count +1; 10 if count = 1 then signal(empty); 11 end; 12 13 procedure remove: 14 begin: 15 if count = 0 then wait(empty); 16 remove_item; 17 count := count - 1; 18 if count = N - 1 then signal(full); 19 end; 20 21 count := 0; 22 23 procedure producer: 24 begin 25 while true do 26 begin 27 produce_item; 28 ProducerConsumer.enter 29 end 30 end 31 32 procedure comsumer: 33 begin 34 while true do 35 begin 36 ProducerConsumer.remove 37 consume_item; 38 end 39 end
3. 进程间的通信机制:
信号量机制太过于底层化,而monitor机制是面向编程语言设计的。这两种机制在面对分布式环境下的进程间的同步问题时显得力不从心。
所以通信机制被引入,同时被引入的还用两个原语: SEND 和 RECEIVE,这两个原语和信号量一样属于系统调用,而不是像monitor一样作为编程语言的一种机构。
send 和 receive 的库函数形式:
1 send(destination,&message); 2 recieve(source,&message);
消息传递系统的设计会遇到许多复杂的问题,特别是两台不同主机上的进程之间的通信问题: 当网络上两个进程通信时,信号很可能会因为各种原语而丢失掉。这样消息的发送者和消息的接受者之间必须达成这样的一个协议: 当接收者接收到一条消息之后,向消息的发送者返回一条acknowedgement确认信息,如果发送者在一段时间内没有接收到这条acknowledge信息,就再发送一条相同的消息。