软、硬件方法解决进程互斥问题

电子说

1.3w人已加入

描述

一些概念:

1.临界资源(critical resource):系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源(或互斥资源)。

2.临界区(互斥区)(critical section(region)):各个进程中对某个临界资源(互斥资源)实施操作的程序片段。

3.进程互斥(mutual exclusive):由于各进程要求使用共享资源(变量、文件等),而这些资源需要排他性使用,因此各进程之间竞争使用这些资源,这一关系称为进程互斥。

4.进程同步(synchronization):指系统中多个进程中发生的事件存在某种时序关系,需要相互合作,共同完成一项任务。具体地说,一个进程运行到某一点时,要求另一伙伴进程为它提供消息,在未获得消息之前,该进程进入阻塞态,获得消息后被唤醒进入就绪态。

软、硬件方法解决进程互斥问题:

1.软件解法:

(1).Dekker算法:

//进程P:
//...
pturn = true;
{
  if (turn == 2) 
  {
    pturn = false;
    while (turn == 2);
    pturn = true;
  } 
}
/*...
临界区
...*/
turn = 2;
pturn = false;
//...

//进程Q:
//...
qturn = true;
while (pturn) 
{
  if (turn == 1) 
  {
    qturn = false;
    while (turn == 1);
    qturn = true;
  } 
}
/*...
临界区
...*/
turn = 1;
qturn = false;
//...

(2).Peterson算法:

#define FALSE 0
#define TRUE 1
#define N 2 // 进程的个数
int turn; // 轮到谁? 
int interested[N];// 兴趣数组,初始值均为FALSE

void enter_region ( int process)// process = 0 或 1 
{ 
  int other;// 另外一个进程的进程号 
  other = 1 - process; 
  interested[process] = TRUE;// 表明本进程感兴趣 
  turn = process;// 设置标志位 
  while( turn == process && interested[other] == TRUE); //循环
}

void leave_region ( int process) 
{ 
  interested[process] = FALSE;// 本进程已离开临界区 
}

//进程i:
//...
enter_region ( i );
/*...
临界区
...*/
leave_region ( i );
//...

(虽然自选锁在一定程度上会白白浪费CPU时间片,但是在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。)

2.硬件解法:

有中断屏蔽方法、“测试并加锁”指令、“交换指令”等方法。

同步机制其一:信号量及P、V操作:

(1).信号量:一个整数值,用于进程间传递信息。

struc semaphore
{
  int count;
  queueType queue;
}

对信号量可以施加的操作只有三种:初始化、P和V。

(2).P、V操作:均为原语操作

semaphore s = 1;
//一种实现方式
P(s) //P操作,又叫down或semWait操作
{
  s.count --;
  if (s.count < 0)
  {
    /*  
    该进程状态置为阻塞状态;
    将该进程插入相应的等待队列s.queue末尾;
    重新调度;
    */
  }
}

V(s)//V操作,又叫up或semSignal操作
{
  s.count ++;
  if (s.count < = 0)
  {
    /*
    唤醒相应等待队列s.queue中等待的一个进程;
    改变其状态为就绪态,并将其插入就绪队列;
    */
  }
}

最初提出的是二元信号量(解决互斥),之后推广到一般信号量(多值)或计数信号量(解决同步)。

用信号量解决问题:

1.生产者——消费者问题:

#define N 100 //缓冲区个数
typedef int semaphore; //信号量是一种特殊的整型数据
semaphore mutex =1; //互斥信号量:控制对临界区的访问
semaphore empty =N;  //空缓冲区个数
semaphore full = 0; //满缓冲区个数 

void producer(void) //生产者进程
{ 
  int item; 
  while(TRUE) 
  { 
    item=produce_item(); 
    P(&empty); 
    P(&mutex); 
    insert_item(item); //临界区
    V(&mutex) 
    V(&full); 
  } 
}

void consumer(void) //消费者进程
{ 
  int item;
  while(TRUE) 
  {
    P(&full);
    P(&mutex);
    item=remove_item();//临界区
    V(&mutex);
    V(&empty);
    consume_item(item);
  }
}
//两个P操作的顺序不能颠倒,会引起死锁,
//V操作的顺序可以颠倒,但是会引起临界区扩大等问题。

2.读者——写者问题:

问题概述:多个进程共享一个数据区,这些进程分为两组:读者进程——只读数据区中的数据,写者进程——只往数据区写数据。允许多个读者同时执行读操作;不允许多个写者同时操作;不允许读者、写者同时操作。

第一类——读者优先:

读者执行:当无其他读、写者时;

当有其他读者在读时;

写者执行:当无其他读、写者时;

typedef int semaphore;
int rc = 0; //reader counter,共享变量
semaphore rw_mutex = 1;//读写临界区保护锁
semaphore rc_mutex = 1;//有多个读者时,rc是我们人为引进的一个
                       //临界区资源,也需要提供锁保护
//读者进程:
void reader(void)
{
  while (TRUE) 
  {
    P(rc_mutex);//对rc上锁
    rc = rc + 1;
    if (rc == 1) //如果是第一个读者
      P(rw_mutex);//对读写临界区上锁
    V(rc_mutex);//对rc操作完毕,解锁
    read();//读写临界区,读操作
    P(rc_mutex);
    rc = rc - 1;
    if (rc == 0) //如果是最后一个读者
      V(rw_mutex);//释放读写临界区
    V(rc_mutex);
}

void writer(void)
{
  while (TRUE) 
  {
    P(rw_mutex);
    write();
    V(rw_mutex);
  }
}

另外两类——写者优先、公平竞争:

多进程对共享资源互斥访问及进程同步的经典问题

设有一文件F,多个并发读进程和写进程都要访问,要求:

读写互斥

写写互斥

允许多个读进程同时访问

采用记录型信号量机制解决

较常见的写法:

semaphore fmutex=1, rdcntmutex=1;
//fmutex --> access to file; rdcntmutex --> access to readcount
int readcount = 0;
void reader(){
    while(1){
        wait(rdcntmutex);
        if(0 == readcount)wait(fmutex);
        readcount = readcount + 1;
        signal(rdcntmutex);
        //Do read operation ...
        wait(rdcntmutex);
        readcount = readcount - 1;
        if(0 == readcount)signal(fmutex);
        signal(rdcntmutex);
    }
}
void writer(){
    while(1){
        wait(fmutex);
        //Do write operation ...
        signal(fmutex);
    }
}

读进程只要看到有其他读进程正在访问文件,就可以继续作读访问;写进程必须等待所有读进程都不访问时才能写文件,即使写进程可能比一些读进程更早提出申请。所以以上解法实际是 读者优先 的解法。如果在读访问非常频繁的场合,有可能造成写进程一直无法访问文件的局面....

为了解决以上问题,需要提高写进程的优先级。这里另增加一个排队信号量:queue。读写进程访问文件前都要在此信号量上排队,通过区别对待读写进程便可达到提高写进程优先级的目的。另外再增加一个 writecount 以记录提出写访问申请和正在写的进程总数:

semaphore fmutex=1, rdcntmutex=1, wtcntmutex=1, queue=1;
//fmutex --> access to file; rdcntmutex --> access to readcount
//wtcntmutex --> access to writecount
int readcount = 0, writecount = 0;
void reader(){
    while(1){
        wait(queue);
        wait(rdcntmutex);
        if(0 == readcount)wait(fmutex);
        readcount = readcount + 1;
        signal(rdcntmutex);
        signal(queue);
        //Do read operation ...
        wait(rdcntmutex);
        readcount = readcount - 1;
        if(0 == readcount)signal(fmutex);
        signal(rdcntmutex);
    }
}
void writer(){
    while(1){
        wait(wtcntmutex);
        if(0 == writecount)wait(queue);
        writecount = writecount + 1;
        signal(wtcntmutex);
        wait(fmutex);
        //Do write operation ...
        signal(fmutex);
        wait(wtcntmutex);
        writecount = writecount - 1;
        if(0 == writecount)signal(queue);
        signal(wtcntmutex);
    }
}

每个读进程最开始都要申请一下 queue 信号量,之后在真正做读操作前即让出(使得写进程可以随时申请到 queue)。而只有第一个写进程需要申请 queue,之后就一直占着不放了,直到所有写进程都完成后才让出。等于只要有写进程提出申请就禁止读进程排队,变相提高了写进程的优先级。

通过类似思想即可实现读写进程的公平竞争:

semaphore fmutex=1, rdcntmutex=1, queue=1;
//fmutex --> access to file; rdcntmutex --> access to readcount
int readcount = 0;
void reader(){
    while(1){
        wait(queue);
        wait(rdcntmutex);
        if(0 == readcount)wait(fmutex);
        readcount = readcount + 1;
        signal(rdcntmutex);
        signal(queue);
        //Do read operation ...
        wait(rdcntmutex);
        readcount = readcount - 1;
        if(0 == readcount)signal(fmutex);
        signal(rdcntmutex);
    }
}
void writer(){
    while(1){
        wait(queue);
        wait(fmutex);
        signal(queue);
        //Do write operation ...
        signal(fmutex);
    }
}

读进程没变,写进程变成在每次写操作前都要等待 queue 信号量。

课本上一般只会写第一种解法吧。看了后两种方法即可发现,在第一个解法中,fmutex 信号量实际是双重身份,首先实现对文件的互斥访问,其次起到了和后面排队信号量 queue 相同的作用,只不过在那种排序下只能是读者优先。如果直接看过后两种解法,应该会有更清楚的理解吧。

同步机制其二:管程机制:

1.管程:由关于共享资源的数据结构及在其上操作的一组过程组成(进程只能通过调用管程中的过程来间接地访问管程中的数据结构),是一种高级同步机制。

2.管程两个重要特性:

管程是互斥进入的:为了保证管程中数据结构的数据完整性,管程的互斥性是由编译器负责保证的。

管程中设置条件变量及等待/唤醒操作(wait/signal):可以让一个进程或线程在条件变量上等待(此时,应先释放管程的使用权),也可以通过发送信号将等待在条件变量上的进程或线程唤醒

3.分类:

P进入管程,执行等待操作并释放管程互斥权,此时Q进入管程,唤醒P进程,管程中就有了两个活动进程,根据对这种情况的处理,分为:

Hoare管程:Q(唤醒者)等待,P(被唤醒者)执行;

MESA管程:P等待Q继续执行;

Hansen管程:规定唤醒操作为管程中最后一个可执行操作。

4.Hoare管程简介:

算法

因为管程是互斥进入的,所以当一个进程试图进入一个已被占用的管程时,应当在管程的入口处等待,为此,管程的入口处设置一个进程等待队列,称作入口等待队列。

如果进程P唤醒进程Q,则P等待Q执行;如果进程Q执行中又唤醒进程R,则Q等待R执行;……,如此,在管程内部可能会出现多个等待进程,在管程内需要设置一个进程等待队列,称为紧急等待队列,紧急等待队列的优先级高于入口等待队列的优先级。

条件变量——在管程内部说明和使用的一种特殊类型的变量(定义一个条件变量c,var c:condition;)。

对于条件变量,可以执行wait和signal操作:

wait(c): 如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的互斥权,执行此操作的进程进入c链末尾。

signal(c): 如果c链为空,则相当于空操作,执行此操作的进程继续执行;否则唤醒第一个等待者,执行此操作的进程进入紧急等待队列的末尾。

审核编辑:汤梓红
 
 


 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分