zoukankan      html  css  js  c++  java
  • 操作系统:进程同步三大经典问题

    日期:2019/4/15

    内容:进程同步;生产者与消费者;读写者;哲学家进餐;信号量机制。

    一、生产者与消费者问题

    1.1 版本1

    • 代码

    void producer()

    {

        while (count == n)

            ;

        buff[in] = produce_item();

        in = (in + 1) % n;

        count++;

    }

    void consumer()

    {

        while (count == 0)

            ;

        item = buff[out];

        print(item);

        out = (out + 1) % n;

        count--;

    }

    • 存在问题

      >>两个while循环一直在"忙等",不符合进程同步的"让权等待"原则。

      >>对于count变量的访问没有保护。(需要加锁保护)

    1.2 版本2:使用信号量

    • 代码

    semaphore empty = n, full = 0;

    void producer()

    {

        while (true)

        {

            wait(empty);

            buffer[in] = produce_item();

            in = (in + 1) % n;

            signal(full);

        }

    }

    void consumer()

    {

        while (true)

        {

            wait(full);

            item = buffer[out];

            print(item);

            out = (out + 1) % n;

            signal(empty);

        }

    }

    • 存在问题

      >>如果有2个producer进程,empty>=2时,同时进入wait(empty)之后的临界区,对于buff的写和in的写产生竞争。

      >>如果有2个consumer进程,full>=2时,同时进入wait(full)之后的临界区,对于out的写产生竞争。

    1.3 版本3:临界区加锁(正确版本)

    • 代码

    semaphore pmutex = 1, cmutex = 1;

    semaphore empty = n, full = 0;

    void producer()

    {

        while (true)

        {

            wait(empty);

            wait(pmutex);

            buff[in] = produce_item();

            in = (in + 1) % n;

            signal(pmutex);

            signal(full);

        }

    }

    void consumer()

    {

        while (true)

        {

            wait(full);

            wait(cmutex);

            item = buff[out];

            print(item);

            out = (out + 1) % n;

            signal(cmutex);

            signal(empty);

        }

    }

    注:教材对于producer和consumer的临界区都使用了同一个mutex,表示producer和consumer互斥进入临界区。但是个人感觉似乎没必要,因为producer和consumer对于buff的访问不存在竞争关系,只需要保证多个producer进程之间互斥,多个consumer进程之间互斥即可。

    二、读者与写者问题

    对于多个进程访问同一文件:

    • 写者:可以读,也可以写
    • 读者:只读
    • 允许多个读者同时读
    • 某一写者在写,不允许其他读写操作

    R

    R

    1

    R

    W

    0

    W

    R

    0

    W

    W

    0

    与生产者消费者的区别:

    • 生产者不仅仅是写进程,还必须调整in指针,但是在读写者问题当中每个进程的文件读写指针是相互独立的。
    • 消费者同理。

    2.1 版本1

    • 代码

    semaphore mutex = 1;

    void writer()

    {

        while (true)

        {

            wait(mutex);

            write_operation();

            signal(mutex);

        }

    }

    void reader()

    {

        while (true)

        {

            wait(mutex);

            read_operation();

            signal(mutex);

        }

    }

    • 问题

      >>不满足"同时读"。

    2.2 版本2:增加读者计数器

    • 代码

    semaphore wmutex = 1;

    void writer()

    {

        while (true)

        {

            wait(wmutex);

            write_operation();

            signal(wmutex);

        }

    }

    void reader()

    {

        while (true)

        {

            if (reader_count == 0)

                wait(wmutex);

            reader_count++;

     

            read_operation();

     

            reader_count--;

            if (reader_count == 0)

                signal(wmutex);

        }

    }

    • 问题

      >>可以允许多个reader进程同时读,但是对reader_count的访问存在竞争。

    2.3 版本3:给reader_count加锁

    rmutex实际上只用于保护reader_count被正确更新。

    • 代码

    semaphore wmutex = 1, rmutex = 1;

    int reader_count = 0;

    void writer()

    {

        while (true)

        {

            wait(wmutex);

            write_operation();

            signal(wmutex);

        }

    }

    void reader()

    {

        while (true)

        {

            if (reader_count == 0)

                wait(wmutex);

            wait(rmutex);

            reader_count++;

            signal(rmutex);

     

            read_operation();

     

            wait(rmutex);

            reader_count--;

            signal(rmutex);

            if (reader_count == 0)

                signal(wmutex);

        }

    }

    • 问题

      >>举例说明红色部分代码问题,reader1和reader2同时执行,同时读取reader_count均为0,那么(假设)先执行reader1的wait1(wmutex),reader1进入阻塞队列;后执行reader2的wait2(wmutex),reader进入阻塞队列。(wait是原语操作,1和2必有先后之分)。但是在reader2执行的时候reader_count的值应为1(但实际是0),这就会使reader2成为僵死进程。

    2.4 正确版本1:读者优先

    • 代码

    semaphore wmutex = 1, rmutex = 1;

    int reader_count = 0;

    void writer()

    {

        while (true)

        {

            wait(wmutex); //保证了WW互斥

            write_operation();

            signal(wmutex);

        }

    }

    void reader()

    {

        while (true)

        {

            wait(rmutex); //保证只能有一个reader访问reader_count

            if (reader_count == 0)

                wait(wmutex);

            reader_count++;

            signal(rmutex);

     

            read_operation();

     

            wait(rmutex); //保证只能有一个reader访问reader_count

            reader_count--;

            if (reader_count == 0)

                signal(wmutex);

            signal(rmutex);

        }

    }

    • 问题

      >>当读者进程≥1时,随后读者进程直接进入临界区,这是读者优先的表征。

      >>写者饿死问题。

      >>假设有进程{R1, W1, R2, R3, ..., Rn}

      >>>>执行R1,那么wmutex变为0,执行read_operation

      >>>>执行W1,wmutex变为-1,阻塞W1

      >>>>执行R2,wmutex不变,执行read_operation

      >>>>对于若干Ri,均是如此,如果CUP资源不足,Ri会进入就绪队列

      >>>>那么W1则很长时间无法调度,就算被siganl操作移入就绪队列也是在队列尾部,产生写者饿死问题。

    2.5 正确版本2:写者优先

    解决写者饿死问题:保证一个写进程想写时(即使它有可能进入阻塞队列),不允许新的读进程访问临界区。(注意这并不是为了解决"读和写不能同时进行")。上面读者优先的症结在于写进程想写,但是读进程优先,不断地进入临界区,即读的调度优先级比写高,从而导致读者饿死。

    • 代码

    int reader_count = 0, writer_count = 0;

    semaphore x = 1, y = 1, z = 1;

    semaphore wmutex = 1, rmutex = 1;

    void writer()

    {

        while (true)

        {

            wait(y);

            if (writer_count == 0)

                wait(rmutex);

            writer_count++;

            signal(y);

     

            wait(wmutex);

            write_operation();

            signal(wmutex);

     

            wait(y);

            writer_count--;

            if (writer_count == 0)

                wait(rmutex);

            signal(y);

        }

    }

    void reader()

    {

        while (true)

        {

            wait(z);

            wait(rmutex);

            wait(x);

            if (reader_count == 0)

                wait(wmutex);

            reader_count++;

            signal(x);

            signal(rmutex);

            signal(z);

     

            read_operation();

     

            wait(x);

            reader_count--;

            if (reader_count == 0)

                wait(wmutex);

            signal(x);

     

        }

    }

    • 解析
      • x:控制reader_count的访问竞争。
      • y:控制writer_count的访问竞争。
      • wmutex:控制有写进程在写的时候,读进程不能进入临界区。
      • rmutex:写进程先把rmutex拿到,保证在写进程运行时,其他所有读进程均无法进入临界区(只能阻塞在rmutex队列上,见红色代码)。
      • z:保证了在rmutex的阻塞队列上,只有一个读进程在排队,其余所有读进程在等待rmutex之前,在z的队列上排队。(尝试把wait(z)和signal(z)去掉理解一下)如果没有z,则都在rmutex上排队。
      • 为什么需要z?在rmutex上不允许长的排队,否则写进程不能跳过这个长队列。
      • 举例说明:{R1, W1, R2, R3, ..., Rn}
        • 如果有z,reader1先wait1,rmutex=0;然后writer再wait(rmutex),rmutex=-1;后面尽管有再多的reader都在堵塞在z。此时,只需要等待reader1执行signal(rmutex),writer即能够进入就绪状态,优先于z中阻塞的reader。
        • 如果无z,writer和所有的reader都进入rmutex排队,实质上无法保证writer优先于reader。

    三、哲学家进餐问题

    • 问题描述

      哲学家需要只吃饭和思考,需要用2把叉子才能吃饭。叉子只能用自己座位两侧的。需要避免死锁和饥饿。

    3.1 版本1

    • 代码

    semaphore fork[5] = {1, 1, 1, 1, 1};

    void philosopher(int i)

    {

        while (true)

        {

            think();

            wait(fork[i]);

            wait(fork[(i + 1) % 5]);

            eat();

            signal(fork[i]);

            signal(fork[(i + 1) % 5]);

        }

    }

     

    void main()

    {

        for (int i = 0; i < 5; i++)

            create_process(philosopher(i));

    }

    • 问题

      >>死锁。每个人同时拿起自己左边的叉子,即philosopher[i]拿起fork[i],那么每个人在wait(fork[(i + 1) % 5])上都会阻塞。

    3.2 版本2(正确版本)

    解决方案:同时只允许4个人进入房间就餐,那么即至少能保证有1人可以拿到2个fork。

    • 代码

    semaphore fork[5] = {1, 1, 1, 1, 1};

    semaphore room = 4;

    void pholosopher(int i)

    {

        think();

        wait(room);

        wait(fork[i]);

        wait(fork[(i + 1) % 5]);

        eat();

        signal(fork[i]);

        signal(fork[(i + 1) % 5]);

        signal(room);

    }

    void main()

    {

        for (int i = 0; i < 5; i++)

            create_process(philosopher(i));

    }

    • 解析

      保证不会死锁和饥饿。(管程解决方案待续)

  • 相关阅读:
    leetcode1030之距离顺序排列矩阵单元格
    leetcode56之合并区间
    leetcode976之三角形最大周长
    leetcode922----按奇偶排序数组
    leetcode198之打家劫舍问题
    leetcode350之实现求解两数组交集(包含重复元素)
    【Python错误】日常记录(持续更新)
    【JavaScript】Lodash在React Native中的使用
    【Python】BeautifulSoup的使用
    转载【Python】python正则表达式详解
  • 原文地址:https://www.cnblogs.com/sinkinben/p/10713536.html
Copyright © 2011-2022 走看看