经典同步问题

进程同步:
进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源,并能很好的互相合作,从而使程序的执行具有可再现性。

临界资源:
是在一段时间内只允许一个进程访问的资源。系统中的大多数的物理设备,以及栈、变量、表格,都属于临界资源,这些进程间应采用互斥方式,实现对这些资源的共享。

临界区:
每个进程中访问临界资源的代码称为临界区。
若能保证进程互斥的进入自己的临界区,便可实现进程对临界资源的互斥访问。如果某一时间临界资源未被访问,进程便可进入临界区对该资源进行检查,看是否被访问;如果某一时间该临界资源被访问,该进程不能进入临界区。

while(TRUE)
{
    进入区
    临界区
    退出区
    剩余区
}

同步机制应遵循的规则:
1.空闲让进:
无进程处于临界区时,表名临界资源处于空闲状态,允许一个请求进入临界资源区的进程立即进入自己的临界区。
2.忙则等待:
当有进程进入临界区时,表明资源正在被访问,其他想进入该临界区的资源必须等待,以保证对临界资源的互斥访问。
3.有限等待:
对要求访问资源的进程,应保证在有限的时间内能进入自己的临界区,以避免陷入“死等”状态。
4.让权等待:
当进程不能进入自己的临界区时,应立即释放处理机,以避免进程进入“忙等”状态。

实际上,在对临界区资源进行管理的时候,可以将标志看做一个“锁”,“锁开”进入,“锁关”等待, 起初锁是打开的。每个要进入临界资源的进程必须先对锁进程测试,当锁未开时,则必须等待,直至锁被打开。反之,当锁是打开的,则应立即把锁锁上,以阻止其他进程进入临界区。显然,测试和关锁必须是连续的,不允许分开执行。

解决临界区的问题:
1.关中断是最简单实现互斥的方法。在进入锁测试之前关闭中断,直到完成锁测试并上锁后才能打开中断。
因此,进程在临界区执行期间,计算机系统不断相应中断,从而不会引发调度,也就不会发生进程或线程切换。保证了对锁测试的和关锁测试的完整性和连续性。

缺点:
滥用关中断权利可能导致严重后果;
关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力;
不适用于多处理器系统,因为在一个处理器上关中断并不能防止进程在其他处理器上执行相同的代码。

经典进程同步问题

1.生产者-消费者问题

问题描述:有一群生产者进程在生产产品,并将这些产品提供给消费者进程进行消费,为生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将其所生产的产品放入一个缓冲区中;消费者进程可以从一个缓冲区中取走产品去消费。

尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,既不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。

我们可以利用一个数组buffer来表示上述具有n个缓冲区的缓冲池。每投入一个产品时,缓冲池buffer中暂存产品的数组指针in加1.由于这里由buffer组成的缓冲池是被组织成循环缓冲的,故in=(in+1)%n表示+1操作。输出指针out,out=(out+1)%n,当(in+1)%n==out时,表示缓冲池已满;当in==out时,表示缓冲池为空。此外,还引入一个整型变量count,初始值为0,每当生产者进程向缓冲池中投放(或取走)一个产品后,count加1(或减1)。注意生产者进程和消费者进程共享这些变量。

虽然生产者程序和消费者程序单独看都是正确的,但若并发执行就会出现差错,问题就在于count加1(或减1)操作在机器语言中被分为三部分:
register=count;
register=register+1;
count=register;
我们知道,count就是一个不能共同访问的临界资源。

2.哲学家就餐问题

问题描述:有5位哲学家,围绕一张圆桌吃饭,桌子上放着5根筷子,每两个哲学家之间放一支。哲学家的动作包括思考和进餐,进餐时需要同时拿到左右两边的叉子,思考时将两支叉子放回原处。
问题:如何保证哲学家的动作有序进行?
经典同步问题_第1张图片

方案一:
利用信号量解决

#define N 5   //哲学家个数
semaphore chop[5];//信号量初值为1
void phil(int i)//哲学家编号:0-4
{
   while(TRUE){
     think();//哲学家思考
     P(chop[i]);//拿左边的筷子
     P(chop[i+1]%N);//拿右边的筷子
     eat();//进食
     V(chop[i]);//放下左边的筷子
     V(chop[i+1]%N);//放下右边的筷子
     }
}

缺点:当每个哲学家都先拿左边的筷子,都陷入等待状态,进入死锁的状态。

方案二:

#define N 5   //哲学家个数
semaphore chop[5];//信号量初值为1
semaphore mutex;//互斥信号量,初值1
void phil(int i)//哲学家编号:0-4
{
   while(TRUE){
     think();//哲学家思考
     P(mutex);//进入临界区
     P(chop[i]);//拿左边的筷子
     P(chop[i+1]%N);//拿右边的筷子
     eat();//进食
     V(chop[i]);//放下左边的筷子
     V(chop[i+1]%N);//放下右边的筷子
     V(mutex);//退出临界区
     }
}

保证大家顺序吃饭,互斥访问正确,但只允许一人进餐,效率低。

方案三:

#define N 5   //哲学家个数
semaphore chop[5];//信号量初值为1
void phil(int i)//哲学家编号:0-4
{
   while(TRUE){
     think();//哲学家思考
     if(i%2 == 0){
         P(chop[i]);//拿左边的筷子
         P(chop[i+1]%N);//拿右边的筷子
     }else{
         P(chop[i+1]%N);//拿右边的筷子
         P(chop[i]);//拿左边的筷子
     }
     eat();//进食
      V(chop[i]);//放下左边的筷子
      V(chop[i+1]%N);//放下右边的筷子
     }
}

偶数编号的哲学家先拿左边,再拿右边;奇数编号的哲学家先拿右边的,再拿左边的。可以同时有两位哲学家进行就餐,提高了进餐效率。

方案四:
除了方案三的解决方法外,我们可以在方案二的基础上增加以下约束条件:
至多只允许四个哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够就餐,并在用完时释放出他使用过的两只筷子,从而使更多的哲学家能够就餐。

方案五:
利用AND信号量机制解决,即仅当哲学家左右两边的筷子均可使用时,才允许进餐。

semaphore chop[5];
do{
   think();
   P(chop[(i+1)%N],chop[i]);//左右筷子同时拿起
   eat();
   V(chop[(i+1)%N],chop[i]);//左右筷子同时释放
}whiel(TRUE);

3.读者-写者问题

问题描述:对共享数据的访问有两类使用者,读者和写者,读者只能读数据,不修改;写者可以读取和修改数据。“读-读”允许,“读-写”互斥,“写-写”互斥,即允许有多个读者读,没有写者时读者才可读,没有读者时写者才可写,没有其他写者时写者才可写。

semaphore rmutex = 1; wmutex = 1;//互斥锁
int readcount = 0;
void reader(){
     do{
          P(rmutex);//关锁
          if (readcount == 0)wait(wmutex);//把写操作锁住,只需要在第一次读时
          readcount++;//读者数目+1
          V(rmutex);//开锁
          read();

          P(rmutex);//关锁
          readcount--;//读者数目-1
          if (readcount == 0)V(wmutex);//没有读者,执行写操作
          V(rmutex);//开锁
     } while (TRUE);
}
void writer(){
     do{
          P(rmutex);
          write();
          V(rmutex);
     } while (TRUE);
}
void main(){
     cobegin
          reader(); writer();
     coend
}

readcount记录读者的个数,当readcount=0,表示尚无读者在读,将写操作上锁(只需在第一次读时),若写操作执行成功,读操作便可进行读,readcount+1,执行读操作后,readcount-1。若没有读者读,便可进行写操作。

你可能感兴趣的:(操作系统,进程同步,信号量)