• 并发控制:同步 (条件变量、信号量、生产者消费者和哲♂学家吃饭问题)


    哲学家吃饭问题 (E. W. Dijkstra, 1960)

    哲学家 (线程) 有时思考,有时吃饭

    • 吃饭需要同时得到左手和右手的叉子
    • 当叉子被其他人占有时,必须等待,如何完成同步?
      • 如何用互斥锁/信号量实现?

    失败与成功的尝试

    失败的尝试


    成功的尝试 (万能的方法)

    mutex_lock(&mutex);
    while (!(avail[lhs] && avail[rhs])) {
      wait(&cv, &mutex);
    }
    avail[lhs] = avail[rhs] = false;
    mutex_unlock(&mutex);
    
    mutex_lock(&mutex);
    avail[lhs] = avail[rhs] = true;
    broadcast(&cv);
    mutex_unlock(&mutex);

    忘了那些复杂的同步算法吧!

    你可能会觉得,管叉子的人是性能瓶颈

    • 一大桌人吃饭,每个人都叫服务员的感觉
    • Premature optimization is the root of all evil (D. E. Knuth)

    抛开 workload 谈优化就是耍流氓

    • 吃饭的时间通常远远大于请求服务员的时间
    • 如果一个 manager 搞不定,可以分多个 (fast/slow path)

    Conditional Variables (条件变量, CV)

    把 pc.c 中的自旋变成睡眠

    • 在完成操作时唤醒

    条件变量 API

    • wait(cv, mutex)
      • 调用时必须保证已经获得 mutex
      • 释放 mutex、进入睡眠状态
    • signal/notify(cv) 私信:走起
      • 如果有线程正在等待 cv,则唤醒其中一个线程
    • broadcast/notifyAll(cv) 所有人:走起
      • 唤醒全部正在等待 cv 的线程

     

    条件变量:实现生产者-消费者

    void Tproduce() {
      mutex_lock(&lk);
      if (count == n) cond_wait(&cv, &lk);
      printf("("); count++; cond_signal(&cv);
      mutex_unlock(&lk);
    }
    
    void Tconsume() {
      mutex_lock(&lk);
      if (count == 0) cond_wait(&cv, &lk);
      printf(")"); count--; cond_signal(&cv);
      mutex_unlock(&lk);
    }
    

    压力测试:pc-cv.c;模型检验:pc-cv.py

    • (Small scope hypothesis)

    条件变量:实现并行计算

    struct job {
      void (*run)(void *arg);
      void *arg;
    }
    
    while (1) {
      struct job *job;
    
      mutex_lock(&mutex);
      while (! (job = get_job()) ) {
        wait(&cv, &mutex);
      }
      mutex_unlock(&mutex);
    
      job->run(job->arg); // 不需要持有锁
                          // 可以生成新的 job
                          // 注意回收分配的资源
    }

     

     

    99% 的实际并发问题都可以用生产者-消费者解决。

    void Tproduce() { while (1) printf("("); }
    void Tconsume() { while (1) printf(")"); }
    

    在 printf 前后增加代码,使得打印的括号序列满足

    • 一定是某个合法括号序列的前缀
    • 括号嵌套的深度不超过 n
      • n=3, ((())())((( 合法
      • n=3, (((())))(())) 不合法
    • 同步
      • 等到有空位再打印左括号
      • 等到能配对时再打印右括号

    条件变量:更古怪的习题/面试题

    有三种线程,分别打印 <>, 和 _

    • 对这些线程进行同步,使得打印出的序列总是 <><_ 和 ><>_ 组合

    使用条件变量,只要回答三个问题:

    • 打印 “<” 的条件?
    • 打印 “>” 的条件?
    • 打印 “_” 的条件?

     

     

    信号量

    复习:互斥锁和更衣室管理

    操作系统 = 更衣室管理员

    • 先到的人 (线程)
      • 成功获得手环,进入游泳馆
      • *lk = ,系统调用直接返回
    • 后到的人 (线程)
      • 不能进入游泳馆,排队等待
      • 线程放入等待队列,执行线程切换 (yield)
    • 洗完澡出来的人 (线程)
      • 交还手环给管理员;管理员把手环再交给排队的人
      • 如果等待队列不空,从等待队列中取出一个线程允许执行
      • 如果等待队列为空,*lk = ✅
    • 管理员 (OS) 使用自旋锁确保自己处理手环的过程是原子的

    更衣室管理

    完全没有必要限制手环的数量——让更多同学可以进入更衣室

    • 管理员可以持有任意数量的手环 (更衣室容量上限)
      • 先进入更衣室的同学先得到
      • 手环用完后才需要等同学出来

    更衣室管理 (by E.W. Dijkstra)

    做一点扩展——线程可以任意 “变出” 一个手环

    • 把手环看成是令牌
    • 得到令牌的可以进入执行
    • 可以随时创建令牌

    “手环” = “令牌” = “一个资源” = “信号量” (semaphore)

    • P(&sem) - prolaag = try + decrease; wait; down; in
      • 等待一个手环后返回
      • 如果此时管理员手上有空闲的手环,立即返回
    • V(&sem) - verhoog = increase; post; up; out
      • 变出一个手环,送给管理员
    • 信号量的行为建模: sem.py

    信号量:实现生产者-消费者

    信号量设计的重点

    • 考虑 “手环” (每一单位的 “资源”) 是什么,谁创造?谁获取?
    void producer() {
      P(&empty);   // P()返回 -> 得到手环
      printf("("); // 假设线程安全
      V(&fill);
    }
    void consumer() {
      P(&fill);
      printf(")");
      V(&empty);
    }
    
    • 在 “一单位资源” 明确的问题上更好用

    哲学家吃饭问题

     

    https://jyywiki.cn/OS/2022/slides/6.slides#/1/3

    【并发控制:同步 (条件变量、信号量、生产者-消费者和哲♂学家吃饭问题) [南京大学2022操作系统-P6]】https://www.bilibili.com/video/BV17T4y1S7RS

  • 相关阅读:
    [Swift]LeetCode922.按奇偶排序数组 II | Sort Array By Parity II
    [Swift]LeetCode921.使括号有效的最少添加 | Minimum Add to Make Parentheses Valid
    [Swift实际操作]八、实用进阶-(5)通过间接代理进行对象间的消息传递
    [Swift]LeetCode927. 三等分 | Three Equal Parts
    [Swift]LeetCode928. 尽量减少恶意软件的传播 II | Minimize Malware Spread II
    我是大牛,我自豪
    程序员拓展人脉的重要性
    程序员拓展人脉的重要性
    2013年总结(3)-活动篇
    2013年总结(3)-活动篇
  • 原文地址:https://www.cnblogs.com/rsapaper/p/16717290.html
Copyright © 2020-2023  润新知