• 12.进程同步与信号量


    【README】

    1.本文内容总结自 B站 《操作系统-哈工大李治军老师》,内容非常棒,墙裂推荐;

    2.进程同步: 让进程间的合作变得合理有序;

    3.通过 信号量 来实现进程同步 ;

    4.操作系统借助信号量实现进程合作,进程走走停停;(进程什么时候停,在哪个地方停特别重要)


    【1】进程合作:多进程共同完成一个任务

     【例1】司机与售票员例子

    司机

    售票员

    While(true) {

      启动车辆;// 等待信号1

      正常运行;

      到站停车;// 发送信号2

    }

    While(true) {

       关门;// 发送信号1

      售票;

      开门; // 等待信号2

    }

    【例2】生产者消费者

    生产者

    消费者

    阻塞直到 counter 不等于 BUFFER_SIZE;

    生产数据后,counter加1,类似向消费者发送信号;

    阻塞直到counter不等于0;

    消费数据后,counter减1,类似向生产者发送信号;


     【2】进程同步

    进程同步定义: 需要让进程走走停停,保证多进程合作的合理有序;


     【3】信号量语义

    1)只发信号还不能解决全部问题;

    • 信号只能表示 有 或者 没有;引入信号量表达更丰富的信息;

    2)问题描述:

    • 当 counter 等于 BUFFER_SIZE 时,生产者1睡眠;
    • 当 counter 等于 BUFFER_SIZE 时,生产者2睡眠;
    • 接着,消费者消费一条数据,counter减1;
    • 此时 counter减1后等于 BUFFER_SIZE    减1,所以消费者会调用 wakeup唤醒生产者1;
    • 接着, 消费者循环消费另外1条数据,counter减1;

    出现的问题

    • 此时counter减1后等于 BUFFER_SIZE 减2 (因为这是第2次消费),因为不满足 counter==BUFFER_SIZE-1 条件,所以消费者不会唤醒生产者2;
    • 显然,counter语义 不足以唤醒所有生产者,所以引入了信号量;
    • (counter记录的是空闲缓冲区数量,而无法记录睡眠的生产者数量,所以根据counter语义无法唤醒所有睡眠的生产者)

    3)信号量
    信号量不仅需要记录睡眠和唤醒,还需要记录当前阻塞的生产者个数等其他信息;

     4)信号量开始工作

    步骤

    生产者与消费者执行详情

    信号量

    sem值

    1

    缓冲区满,生产者P1执行, P1睡眠,信号量减1;则信号量为-1;(信号量-1表示1个生产者睡眠)

    -1

    2

    生产者P2执行, P2睡眠,信号量减1;则信号量为-2(表示2个生产者睡眠)

    -2

    3

    消费者执行1次循环, wakeup唤醒P1后,信号量加1得到-1;(表示1个生产者睡眠)

    -1

    4

    消费者再执行1次循环, wakeup唤醒P2,信号量加1得到0;(没有生产者睡眠)

    0

    5

    消费者在执行一次循环;信号量加1;(表示有1个可用缓冲空间)

    1

    6

    生产者P3执行,信号量减1;

    0

    信号量sem值含义:

    • -2: 有2个生产者阻塞,或者欠生产者队列2个单位缓冲空间;
    • -1: 有1个生产者阻塞,或者欠生产者队列1个单位缓冲空间;
    • 0: 没有生产者阻塞,正常运行;
    • 1: 表示还有1个单位的可用缓冲空间;
    • 2:表示还有2个单位的可用缓冲空间;

    【小结】

    • 接下来,生产者与消费者就可以根据  信号量sem  来决定进程同步,或决定多个进程合作的合理有序执行;


    5)基于信号量的进程合作
    多个进程合作完成一件事,多个进程在执行过程中,执行的推进顺序要合理有序;
    具体地,在执行一定程度后,进程根据信号量判断是否停下来等待;

    • 5.1)生产者:若信号量等于0或负值,则生产者进程等待,且信号量减1;
    • 5.2)消费者:若信号量等于负值,则消费者唤醒一个生产者进程,且信号量加1;
    • 若信号量等于0,则消费者正常执行,且信号量加1;

    【4】信号量实现

    1)信号量定义:一种特殊整型变量,量用来记录,信号用来判断是否睡眠sleep和唤醒wakeup;
    2)信号量代码

    1. // 信号量代码
    2. struct semaphore()
    3. {
    4. // 记录资源个数
    5. int value ;
    6. // 进程阻塞队列(记录在该信号量上等待的进程)
    7. PCB *queue;
    8. }
    9. // 生产者:消费资源(这里消费资源指的是生产者消费一个空闲缓冲区,或一个数组项)
    10. P (semaphore s)
    11. {
    12. s.value--; // 消费资源
    13. if (s.value < 0) {
    14. sleep(s.queue); // 当前生产者进程睡眠
    15. }
    16. }
    17. // 消费者:产生资源 (这里产生资源指的是消费者释放一个空闲缓存区,或一个数组项)
    18. V (semaphore s)
    19. {
    20. s.value++; // 释放资源
    21. if (s.value <=0 ) {
    22. wakeup(s.queue); // 消费者唤醒睡眠的生产者进程
    23. }
    24. }

     【补充】 荷兰语中

    • P表示proberen,即test测试的意思,即生产者;
    • V 表示 verhogen,即increment,增加资源的意思,即消费者;

    【5】用信号量解决生产者消费者问题

     1)信号量解决生产者消费者问题的源代码

    • 下面源代码有3个信号量
      • full:缓存区中数据或内容个数,或已占用的缓冲区个数;
      • empty:空闲缓冲区个数;
      • mutex:互斥信号量,同时只允许1个进程进入代码;
    1. // 1 用文件定义共享缓冲区
    2. int fd = open("buffer.txt");
    3. write(fd, 0, sizeof(in)); // 写入数据到in位置
    4. read(fd, 0, sizeof(out)); // 从out位置读取数据
    5. // 2 信号量的定义和初始化
    6. semaphore full = 0; // 表示缓冲区中已生产的数据(内容)个数,或已用缓冲区个数;
    7. semaphore empty = BUFFER_SIZE; // 表示空闲缓冲区个数
    8. semaphore mutex = 1; // 互斥信号量,同时只能有1个进程进去;
    9. // 3 生产者
    10. Producer(item) {
    11. P(empty); // 生产者先测试empty信号量是否为0(为0表示没有空闲缓冲区)
    12. P(mutex);
    13. // 读取in,把item写入到in的位置上 (生产操作)
    14. V(mutex);
    15. V(full); // 增加数据(内容)个数
    16. }
    17. // 4 消费者
    18. Consumer() {
    19. P(full); // 消费者先测试缓冲区是否存在内容,则没有则阻塞
    20. P(mutex); // 判断是否可以访问文件,mutex是互斥信号量,同时只有1个进程可以访问文件(mutex减1, 获取mutex信号量或锁)
    21. // 读取out,从文件中的out位置读出到item,打印item (消费操作)
    22. V(mutex); // mutex加1,释放mutex 信号量
    23. V(empty);// 消费者在消费完后,增加空闲缓冲区个数
    24. }

  • 相关阅读:
    LeetCode(力扣)763. 划分字母区间Python
    【PSO-RFR预测】基于粒子群算法优化随机森林回归预测研究(Matlab代码实现)
    Haproxy配合Nginx搭建Web集群
    java使用多线程进行批量更新数据
    8.Docker MySQL 主从复制
    Flutter绘制拖尾效果
    深度学习知识点
    MySQL之优化服务器设置(五)
    【元宇宙欧米说】一个科幻 NFT,一场关于创作者经济的探索
    java142-file类的基本创建
  • 原文地址:https://blog.csdn.net/PacosonSWJTU/article/details/125536120