zoukankan      html  css  js  c++  java
  • 经典进程同步问题

    1.信号量(semaphore)

    1.1概念

      信号量是一个计数器,常用来实现进程间的同步和互斥

    1.2允许的两个操作

    1)原子操作:原子操作是不会被中断的操作,要么就不干,要是干了,它就不会被任何东西打断

    2)以下两个均为原子操作

    3)down操作P操作):将信号量的值值减1;当信号量等于0时,再次down操作会让之后的进程进入等待(阻塞态)

    void wait(semaphore S)
    {
        --S.value;
        if (S.value<0)
        {
            add this process to S.L;//进程链表L,记录所有等待该资源的进程
            block(S.L);
        }
    }

    4)up操作V操作):将信号量的值加1;特别的,对一个有进程在其上等待的信号量执行一次up操作后,唤醒在其上等待的其中一个进程进入就绪态

    void signal(semaphore S)
    {
        ++S.value;
        if (S.value<=0)
        {
            remove a process P to S.L;//进程链表L,记录所有等待该资源的进程
            wakeup(P);
        }
    }

    1.3信号值所代表的含义

    1)当信号量S大于0时表示可用的临界资源数,当等于0时,表示刚好用完;

    2)标准定义,信号量只能为非负整数,但是,当信号量S小于0时,也有其意义,这个负值的绝对值表示系统中因申请该类资源而被阻塞的进程数目

    举例:有两个某类资源,四个进程A、B、C、D要用该类资源;

       最开始S=2,当A进入,S=1,当B进入,S=0,表明该类资源刚好用完;

       当C进入时S=-1,表明有一个进程被阻塞了,D进入,S=-2,表明有两个进程被阻塞了;

       当A用完该类资源时,进行V操作,S=-1,释放该类资源,因为S<=0,表明有进程阻塞在该类资源上,于是唤醒C,C进入就绪态,C发现有一个资源可以用,然后直接转换到运行态,使用该资源;

               当B用完该类资源时,进行V操作,S=-0,释放该类资源,因为S<=0,表明有进程阻塞在该类资源上,于是唤醒D,D进入就绪态,D发现有一个资源可以用,然后直接转换到运行态,使用该资源;

    1.4信号量初值与其作用

    1)如果初值为0,用来实现进程间的同步

    2)如果初值为1,用来实现进程间互斥访问

    3)如果初值为N(N一般大于1),用来限制并发数目

    1.5互斥量

    1)信号量的一个特殊版本

    2)值为0或者1,0为加锁状态,1为不加锁状态

    3)当互斥量处于锁住状态时,任何试图再次加锁的行为都将被阻塞

    2.生产者-消费者问题

    2.1问题描述

      两个进程共享一个大小为n的缓冲区,其中一个是生产者,生产数据并将数据放入缓冲区;另一个是消费者,从缓冲区中取出数据。由于缓冲区是临界资源,如何实现缓冲区的互斥访问和生产者、消费者之间的同步

    2.2分析

    使用三个信号量:

    1.full,记录缓冲区满槽数目,用于同步,初值为0;

    2.mutex,用于互斥,初值为1;

    3.empty,记录缓冲区空槽数目,用于同步,初值为n;

    2.3代码

    semaphore full = 0;     //缓冲区满槽数目,同步量
    semaphore mutex = 1;    //互斥量
    semaphore empty = n;    //缓冲区空槽数目
    
    void producer()
    {
        while (1)
        {
            produce an item in nextp;
            down(empty);                //down(empty)一定要在down(mutex)前
            down(mutex);
            add nextp to buffer;
            up(mutex);
            up(full);
        }
    }
    
    void consumer()
    {
        while (1)
        {
            down(full);                    //down(full)一定要在down(mutex)前
            down(mutex);
            remove an item from buffer;
            up(mutex);
            up(empty);
        }
    }

    2.4代码解读

    2.4.1同步

    最开始缓冲区里没有数据,应该达到的效果是:消费者被阻塞,一旦生产者将数据送入缓冲区,消费者被唤醒去取数据,我们来看看是如何实现的:

    1)消费者down(full),full=-1,根据down操作的定义(见上面代码wait函数),full<0,消费者将被送入等待链表被阻塞;

    2)某时刻生产者想生产,“produce an item in nextp; down(empty); down(mutex); add nextp to buffer;up(mutex);”,一气呵成地走下来,然后up(full),full=0,根据up操作的定义(见上面代码signal函数),full<=0,消费者从等待链表中移除,然后被唤醒,变成就绪态,然后生产者彻底走完这次流程,消费者就变成运行态开始继续往下走了

    反之,缓冲区满时,生产者应该被阻塞,一旦消费者取走数据,生产者才被唤醒去把数据放入缓冲区,具体实现过程类似,此处不再赘述。

    2.4.2互斥

    讨论互斥时,为了方便,假设现在缓冲区既不满也不空;

    1)某时刻生产者想生产,“produce an item in nextp; down(empty); down(mutex);”,走到这,mutex由1变为0;

    2)此时如果消费者想进来是进不来的,因为消费者“remove an item from buffer;”之前也需要down(mutex),这样的话,mutex就变成了-1,小于0,根据down操作的定义(见上面代码wait函数),mutex<0,消费者将被送入等待链表被阻塞

    3)所以生产者可以安心地向下走,“add nextp to buffer;”,直到up(mutex)后,mutex=0,根据up操作的定义(见上面代码signal函数),mutex<=0,消费者从等待链表中移除,然后被唤醒,变成就绪态;

    4)然后生产者彻底走完这次流程,消费者就变成运行态开始往下走。

    2.4.3如果生产者和消费者把down(empty)和down(full)在down(mutex)后面,会出现问题:

    1)当生产者将缓冲区放满了,消费者还没有工作,然后生产者又想生产,生产者先down(mutex)再 down(empty),mutex=0,empty=-1,根据down操作的定义(见上面代码wait函数),empty<0,生产者将被送入等待链表被阻塞,等待消费者出马取走数据;

    2)然后过了一会消费者来了,先down(mutex),mutex=-1,根据down操作的定义(见上面代码wait函数),mutex<0,消费者也将被送入等待链表被阻塞;

    3)于是双方都被阻塞,就陷入了无休止的等待。

    3.读者-写者问题

    3.1问题描述

       有读者和写者两组并发程序,共读写一个文件,允许多个读者同时读,在进行写操作时不允许读操作和任何其他的写操作。

    3.2分析

    需要三个互斥量

    1)读者和写者互斥,需要一个互斥量readAndWrite

    2)写者和写者互斥,可以借助readAndWrite

    3)读者和读者不互斥,写者要等读者为0的时候才能写,所以需要一个readCount来记录读者数,并且要保证readCount在更新时不被另一个读者更新,所以还需要一个互斥量mutexReadCount

    4)为了保证写请求的公平,需要一个互斥量gongpingWrite

    3.3代码

     1 int readCount = 0;                //记录正在读的读者数
     2 semaphore mutexReadCount = 1;        //用于保证更新count变量时的互斥
     3 semaphore readAndWrite = 1;    //用于保证读和写的互斥
     4 semaphore gongpingWrite = 1;        //用于保证写和读的相对公平
     5 void writer()
     6 {
     7     while (1)
     8     {
     9         down(gongpingWrite);            //有写请求时,在写请求之后的读请求被禁止
    10         down(readAndWrite);        //读和写互斥
    11         writing;                //写操作
    12         up(readAndWrite);        //读和写互斥
    13         up(gongPingWrite);                //恢复对共享文件的访问
    14     }
    15 }
    16 
    17 void reader()
    18 {
    19     while (1)
    20     {
    21         down(gongpingWrite);            //有写请求时,在写请求之后的读请求被禁止
    22         down(mutexCount);            //互斥访问count变量
    23         if (readCount == 0)            //第一个读进程读共享文件时,写被禁止
    24             down(readAndWrite);
    25         ++readCount;
    26         up(mutexReadCount);                //互斥访问count变量
    27         up(gongpingWrite);                //恢复对共享文件的访问
    28         reading;                //读操作
    29         down(mutexReadCount);            //互斥访问readCount变量
    30         --readCount;
    31         if (readCount == 0)            //所有读者读完,写才被允许
    32             up(readAndWrite);
    33         up(mutexReadCount);                //互斥访问count变量
    34     }
    35 }

    3.3代码解释

    结合注释来看

    1)写者要等读者数量为0的时候才能写:23行和31行

    2)保证count在更新时不被另一个读者更新:22、26、29、33

    3)保证写请求的公平:9、13、21、27

      为什么说保证写请求的公平,如果没有这四行,假设这么一种情况,当一个读者在进行读操作时,其后连续不断的读者的读请求都将被允许,而写者的写请求将被拒绝,陷入长时间等待,等到没有新读者的请求也没有正在写的读者。这就是一种对写请求的不公平。

      这四行就是来解决这个问题的:有一个读者正在进行读操作时,这时有一个写者发起写请求,它要等待count=0才能进行写操作,同时它执行第9行down(gongpingWrite),由于第21行的down(gongpingWrite),这就等于禁止了后续读者的读请求,它只需要等这个正在进行读操作的读者出来就可以了

    4.哲学家就餐问题

    4.1问题描述

      一张圆桌上坐着5个哲学家,每个哲学家之间摆了一根筷子,筷子之间是一碗米饭;哲学家要么思考要么进餐;哲学家思考不影响他人;当哲学家进餐时,试图拿起左右两边的筷子(一根一根地拿),如果筷子在他人手里,则需等待,只有拿到两根筷子才能进餐,进餐完后又进行思考。

    4.2分析

    1)设置取筷子的互斥量mutex;

    2)设置五个取左还是右筷子的互斥量chopstick[5];

    2)对哲学家 i 编号0~4,哲学家i左边的筷子编号为 i,右边的筷子编号(i+1)%5;

    4.3代码

    semaphore mutex = 1;
    semaphore chopstick[5] = { 1,1,1,1,1 };
    
    void P(i)
    {
        while (1)
        {
            down(mutex);    //锁住取左筷子和右筷子的动作
            down(chopstick[i]);
            down(chopstick[(i + 1) % 5]);
            up(mutex);        //解锁,现在其他人可以试着取左筷子和右筷子了,注意:是“试着”
            eat;
            up(chopstick[i]);
            up(chopstick[(i + 1) % 5]);
            think;
        }
    }

     4.4代码解读

     1)使用互斥量mutex的原因:防止死锁

      如果没有mutex,当五个哲学家都想要进餐,分别拿起他们左边的筷子,均执行down(chopstick[i]),chopstick[i]均变为0,筷子被拿光,当他们拿右边的筷子时,执行down(chopstick[(i+1)%5]),chopstick[i]均变为-1,小于0,根据down操作的定义(见上面代码wait函数),五个哲学家将全部被送入等待链表被阻塞,出现死锁。

  • 相关阅读:
    php环境配置中各个模块在网站建设中的功能
    PHP+Apache+MySQL+phpMyAdmin在win7系统下的环境配置
    August 17th 2017 Week 33rd Thursday
    August 16th 2017 Week 33rd Wednesday
    August 15th 2017 Week 33rd Tuesday
    August 14th 2017 Week 33rd Monday
    August 13th 2017 Week 33rd Sunday
    August 12th 2017 Week 32nd Saturday
    August 11th 2017 Week 32nd Friday
    August 10th 2017 Week 32nd Thursday
  • 原文地址:https://www.cnblogs.com/Joezzz/p/9757677.html
Copyright © 2011-2022 走看看