为什么会出现管程
问题:
1.信号量机制的不足:编写困难,易出错
管程的定义
1.是一个特殊的模块
2.有一个名字
3.由关于共享资源的数据结构及在其上操作的一组过程组成
进程与管程的关系
1.进程只能通过调用管程中的过程来间接的访问管程中的数据结构
管程要保证什么
1.作为一种同步机制,管程要解决2个问题
1.互斥,管程是互斥进入的 --为了保证管程中的数据结构的数据完整性,管程的互斥性是由编译器负责保证的
2.同步,管程中设置条件变量及等待/唤醒操作以解决同步问题
1.可以让一个进程或线程在条件变量上等待(此时,应先释放管程的使用权),也可以通过发送信号将等待在条件变量上的进程或线程唤醒
应用管程时遇到的问题
场景:
当一个进入管程的的进程执行等待操作时,它应当释放管程的互斥权
当后面的进入管程的进程执行唤醒操作时(例如P唤醒Q),管程中便存在2个同时处于活动状态的进程
如何解决:
三种处理方法:
1.P等待Q执行(Hoare)
2.Q等待P继续执行(MESA)
3.规定唤醒操作为管程中最后一个可执行的操作
(Hansen并发pascal编程语言做了实现)
HOARE管程
因为管程是互斥进入的,所以当一个进程试图进入一个已被占用的管程时,应当在管程的入口处等待
1.为此,管程的入口处设置进程等待队列,称作入口等待队列
如果进程P唤醒进程Q,则P等待Q执行,如果进程Q执行中又唤醒进程R,则Q等待R执行。如此,在管程内部可能会出现多个等待进程
1.在管程内需要设置一个进程等待队列,称为紧急等待队列,紧急等待队列的优先级高于入口等待队列的优先级
条件变量的实现
1.条件变量 -- 在管程内部说明和使用的一种特殊类型的变量
2.var c:condition
3.对于条件变量,可以执行wait和signal操作
wait(c)
如果紧急等待队列非空,则唤醒第一个等待者,否则释放管程的互斥权,执行次操作的进程进入c链末尾
signal(c)
如果c键为空,则相当于空操作,执行此操作的进程继续执行,否则唤醒第一个等待者,执行此操作的进程进入紧急等待队列的末尾
管程的实现
1.直接构造 ->效率高
2.间接构造 ->用某种已经实现的同步机制去构造
用管程解决生产者消费者问题
//伪代码实现
monitor ProducerConsumer
condition full emptery;
integer count;
producers insert(items:interger);
begin
if count == N then wait(full);
insert_item(item);count++;
if count == 1 then signal(empty);
end;
function remove:integer;
begin
if count == 0 then wait(empty)
remove = remove.item;count--;
if count == N -1 then signal(full)
end
count=0
end monitor
//假设编译器提供了相应的安全接口
procdure producer;
begin
while true do
begin
item = produce_item;
ProducerConsume.insert(item)
end
end
produre consumer
begin
while true do
begin
item = ProducerConsumer.remove()
consume_item(item)
JAVA中的类似机制
public class ProducerConsumer {
static final intN = 100;
static producer p = new producer()
static consumer = new consumer()
static our..monitoer = new our...monitor()
public static void main(String args[]){
p.start()
c.start()
}
static class producer extends Thread{
public void run(){
int item;
while(true){
item = produce;item();
mon.insert(item);
}
}
private int produce item(){...}
}
}
static class consumer extends Thread {
public void run(){
int item
while(true){
item = mon.remove()
consume...item(item);
}
}
private void consume...item(int item){...}
}
static class our...monitor{
private int buffer[] = new int[N]
private int count = 0
public synchronized void insert(int val){
if (count == N)go to sleep()
buffer[h1] = val
h1 = (h1 + 1)%N
count = count + 1
if (count == 1) notify()
}
}
MESA管程
Hoare管程的一个缺点
1.两次额外的进程切换
解决:
1.signal -> notify
2.notify:当一个正在管程中的进程执行notify(x)时,它使得x条件队列得到通知,发信号进程继续执行
使用NOTIFY要注意的问题
1.notify的结果:位于条件队列头的进程在将来合适的适合且当处理器可用时恢复执行
2.由于不能保证在它之前没有其他进程进入管程,因而这个进程必须重新检查条件
1.用while循环取代if语句
3.导致对条件变量至少多一次额外的检测,但不再又额外但进程切换,并且对等待进程在notify之后何时运行没有任何限制
MESA管理:生产者/消费者问题
void append(char x)
{
while(count ==N)cwait(notfull)
buffer[nextin] = x;
nextin = (nextin + 1 )% N
count ++ ;
cnotify(noempty)
}
void take(char x)
{
while(count == 0)cwait(notfull)
x = buffer[nextout];
nextout = [nextout + 1] % N;
count --.
cnotify(notfull)
}
改进notify
对notify的一个很有用的改进
1.给每个条件原语关联一个监视计时器,不论是否被通知,一个等待时间超时的进程将被设为就绪态
2.当该进程被调度执行时,会再次检查相关条件,如果条件满足则继续执行
超时可以防止如下情况的发生
1.当某些进程在产生相关条件的信号之前失败时,等待该条件的进程就会被无限制的推迟执行而处于饥饿状态
引入BROADCAST
broadcast:所有在该条件上等待的进程都被释放并进入就绪队列
1.当一个进程不知道又多少进程将被激活时,这种方式是非常方便的
例子:生产者/消费者问题中,假设insert和remove函数都使用于可变长度的字符块。它不需要知道每个正在等待的消费者准备消耗多少字符,而仅仅执行一个broadcast,所有正在等待的进程都得到通知并再次尝试执行
2.当一个进程难以准确判断将激活哪个进程时,也可以使用广播
HOARE管程与MESA管程的比较
1.MESA管程优于Hoare管程之处在于Mesa管程错误比较少
2.在Mesa管程中,由于每个过程在收到信号后都重新检查管程变量,并且由于使用来while结果,一个进程不正的broadcast广播或发信号notify,不会导致收到信号的程序出错
收到信号的程序将检查相关的变量,如果期望的条件没有满足,它会重新继续等待
管程小结
管程:抽象数据类型
有一个明确定义的操作集合,通过它且只有通过它才能操作该数据类型的实例
实现管程结构必须保证下面几点
1.只能通过管程的某个过程在能访问资源
2.管程是互斥的,某个时刻只能又一个进程或线程调用管程中的过程
条件变量:为提供进程与其他进程通信或同步而引入
1.wait/signal或wait/notify或wait/broadcast
PTHREAS中的同步机制
1.通过互斥量,保护临界区
Pthread...mutex...lock
Pthread...mutex...unlock
2.条件变量,解决同步
Pthread...cond...wait
Pthread...cond...signal
int main(int argc, char ""argv){
pthread...t.pro.con;
pthread...mutex...init(&the...mutex,0);
pthread...cond...init(&condc,0);
pthread...cond...init(&condp,0);
pthread...create(&con,0,consumer,0);
pthread...create(&pro,0,producer,0);
pthread...join(pro,0);
pthread...join(con,0);
pthread...cond...destroy(&condc);
pthread...cond...destroy(&condp);
pthread...mutex...destroy(&the...mutex);
}
#include <stdio.h>
#include <pthread.h>
#define MAX 1000000000
pthread...mutex...t the...mutex
pthread...cond...t condc,condp
int buffer = 0
void *producer(void *ptr)
{
int i ;
for (i = 1 ; i <= MAX; i++){
pthread...mutex...lock(&the..mutex)
while(buffer != 0)pthread...cond...wait(&condp,&the...mutex)
buffer = i;
pthread...cond...signal(&condc);
pthread...mutex...unlock(&the...mutex);
}
pthread...exit(0)
}
void *consumer(void *ptr)
{
int i;
for(i = 1; i < MAX; i++){
pthrea...mutex...lock(&the...mutex)
while (buffer == 0)pthread...cond...wait(&condc,&the...mutex)l
buffer = 0;
pthread...cond...signal(&condp)
pthread...mutex...unlock(&the...mutex)
}
}
PTHREAD_COND_WAIT
1.pthread_cond_wait的执行分解为三个主要动作
1.解锁
2.等待,当收到解除等待的信号(pthread_con_signal)或者(pthread_con_broad_cast)之后,pthread_cond_wait马上需要要做的动作是
3.上锁