zoukankan      html  css  js  c++  java
  • 《操作系统》课程笔记(Ch06-同步)

    竞争条件:多个进程并发访问和操作同一数据,且执行结果与特定访问顺序有关

    防止竞争条件:确保一次只有一个进程可以操作变量,即进程需要进行同步

    临界区问题

    在临界区内进程可能修改公共变量等,当一个进程在临界区内执行时,其他进程不允许进入该临界区执行。

    进程的一般结构

    临界区问题的解决方案要满足三条要求

    • 互斥

      如果Pi在临界区执行,则其他进程不能在其中执行

    • 进步

      如果没有进程在临界区执行,并且有进程要进入临界区,那么只有那些不在剩余区的进程可以参加选择,并且这种选择不能无限推迟

    • 有限等待

      从一个进程要求进入临界区到这个请求被允许,其他进程不能无休止地进入其临界区

    Peterson解决方案

    适合于两个进程Pi、Pj交错执行临界区与剩余区。

    int turn; // 哪个进程可以进入临界区
    boolean flag[2]; // 哪个进程准备进入临界区
    

    进入过程:

    • Pi设置flag[i]=trueturn=j,表示自己已经准备好,并且如果Pj要进入可以进入
    • 考虑双方同时设置的情况,turn最终只会有一个值,只有一个进程真正进入临界区
    • while (flag[j] && turn==j);当对方准备好且轮到对方时,等待,直到对方释放

    硬件同步

    从硬件角度出发解决同步问题。

    单处理器环境

    对于单处理器环境,在修改共享变量时只要禁止中断,就能解决临界区问题。

    多处理器环境

    对于多处理器环境,这种解决方案不可行,因为耗时长、系统效率降低、影响时钟更新中断。给出下面两个方案:

    test_and_set

    原子指令。设该值为真,并返回原值。

    原理

    • lock为false,没有被锁住,则while停止,lock被置true,该进程进入临界区
    • 其他进程看到lock是true,无限while
    • 直到该进程到lock=false释放lock
    • 某个进程重复第一步

    compare_and_swap

    原子指令。仅当值等于期待值时赋新值,返回原值。

    实现同步的原理较为复杂,不作讨论。

    问题

    test_and_set 和 compare_and_swap 能保证互斥,但不能满足有限等待,需要做一定的修改。

    互斥锁

    基于硬件的解决方案太复杂,并且不能由程序员直接使用。互斥锁(mutex lock)更简单。

    每个互斥锁有一个available变量 ,提供acquire()和release()方法用于获取和释放锁。这两个方法的执行必须是原子的。

    互斥锁有多种实现。一种需要忙等待的实现如下。这种互斥锁也被称为自旋锁。

    acquire() {
    	while (!available); // busy wait
    	available = false;
    }
    
    • 自旋锁的优点:当进程等待锁时没有上下文切换,因此等待时间短时,性能更好

    • 自旋锁的缺点:多道程序系统中(多个进程共享CPU),忙等待浪费CPU周期

    信号量

    信号量S是一个整型变量,表征某个资源的可用量。提供两个原子方法:wait()和signal()。

    wait(S) { // 等待并申请
      while (S <= 0); // busy wait
    	S--;
    }
    signal(S) { // 释放
      S++;
    }
    

    上面的实现方案仍有忙等待问题,但信号量是可以解决自旋问题的。当wait()并发现信号量非正时,不是忙等待,而是阻塞自己,把自己放到与信号量相关的等待队列中,并且将进程状态切换为等待状态。当有signal()被调用时,wakeup()等待队列中的某个进程。这时,信号量的功能表现类似于一个外设。

    相关问题

    • 信号量的值可以为负,其绝对值就是等待该信号量的进程数
    • 每个信号量都有一个等待队列
    • wait()和signal()操作本身也是临界区操作,需要硬件实现互斥。多机系统中会导致忙等,但是时间很短

    信号量死锁

    考虑信号量S=Q=1。P0操作序列wait(S),wait(Q),signal(S),signal(Q),P1操作序列wait(Q),wait(S),signal(Q),signal(S)。两个signal可能都无法执行,导致死锁。

    一组进程处于死锁:组内的每个进程都等待一个事件,而该事件只能由组内的另一个进程产生

    饥饿(无限阻塞)

    如果对与信号量有关的链表按LIFO顺序增加和删除进程,那么可能发生无限阻塞。

    优先级反转

    假设有三个进程优先级L<M<H。H需要R,R正被L访问,则H需要等待L用完R。但此时M可以抢占L(尽管M不用资源R),即较低优先级的进程影响了H应该等待多久。

    该问题只出现在具有两个以上优先级的系统中。解决时可以采用优先级继承协议,L临时继承H的优先级,防止M抢占,直到L用完资源R时释放优先级。

    经典同步问题

    有界缓冲问题

    第一读者-写者问题

    有的进程可能只需要读(读者),而其他进程需要读+写(写者),即要求写者在写入时具有独占访问权。

    第一读者-写者问题要求读者不应等待,除非写者已经在使用共享对象。下面的解决方案可能产生饥饿。

    有些系统提供读写锁,锁具有两形式:读锁和写锁。多个进程可以并发获得读锁,但只有一个可以获得写锁。

    读写锁在:①容易识别哪些进程只读,哪些只写;②读者比写者多,从而并发程度可以弥补锁的开销时最有用。

    哲学家就餐问题

    五个哲学家坐在圆桌边,桌上有五只筷子。哲学家要么思考,要么进餐。进餐时需要获得左右两根筷子,但一次只能拿起一根筷子。

    这可能造成死锁,存在一个哲学家永远不能获得需要的第二只筷子。补救措施有:

    • 允许最多四个哲学家坐在桌上
    • 只有两根筷子都可用时,哲学家才在临界区一起拿起
    • 非对称拿起方案,如单号先左后右,双号先右后左

    管程

    信号量存在时序错误的可能性。因此提出一种重要的、高级的同步工具,管程(monitor)。

    结构

    管程结构确保每次只有一个进程在管程内处于活动状态

    • 一组程序员定义的,在管程内互斥的操作
    • 一组变量记录管程实例状态

    管程 = 锁+条件变量(两种同步机制)

    锁:用来互斥,通常由编译器提供;条件变量:控制进程或线程执行顺序。

    管程的中心思想是去运行一个在管程中睡觉的线程

    条件变量

    condition x, y;条件变量用于表示某一个条件,每个条件都有一个与之关联的队列。

    • x.wait():挂起调用该句的进程

    • x.signal():恢复挂起队列中的一个进程Q,如果没有就不产生作用。如果有,则有两种策略:①当前进程等待,Q执行;②当前进程离开管程后Q执行。

    哲学家就餐问题的管程解答(无死锁)

    采取“只有两根筷子都可用时,哲学家才在临界区一起拿起”策略。

    替代方法

    • 事务内存

      在内存上执行事务操作(原子的,可提交或回滚)

    • OpenMP

    • 函数式编程语言

      不允许可变状态,不用关心竞争条件和死锁等问题

  • 相关阅读:
    在js中如何将字符串类型的日期("2020-11-30T02:21:42.000+0000")进行格式化
    微信小程序:报错fail webview count limit exceed
    微信小程序:picker组件实现下拉框效果
    微信小程序:post请求参数放在请求体中还是拼接到URL中需要看后台是如何接收的
    【华为云技术分享】如何用交互式特征工程工具进行数据分析处理
    【云小课】基础服务第25课 容灾演练:平时多练兵,急时保可用!
    【华为云分享】软件工程的迷途与沉思
    WebSocket 从入门到写出开源库
    教你轻松截获 Selenium 中的 Ajax 数据
    【华为云技术分享】Scrum Master如何引导团队中的刺头
  • 原文地址:https://www.cnblogs.com/zxuuu/p/12849423.html
Copyright © 2011-2022 走看看