操作系统 - 同步互斥

并发进程的正确性

独立进程

  • 不和其他进程共享资源或状态
  • 确定性 ==> 输入状态决定结果
  • 可重现 ==> 能够重现起始条件
  • 调度顺序不重要

并发进程

  • 在多个进程间有资源共享
  • 不确定性
  • 不可重现

并发进程的正确性

  • 执行过程是不确定性和不可重现的
  • 程序错误可能是间歇性发生的

进程并发执行的好处

进程需要与计算机中的其他进程和设备进行协作

好处1:共享资源

  • 多个用户使用同一台计算机
  • 银行账号存款余额在多台ATM机操作
  • 机器人上的嵌入式系统协调手臂和手的动作

好处2:加速

  • I/O操作和CPU计算可以重叠(并行)
  • 程序可划分成多个模块放在多个处理器上并行执行

好处3:模块化

  • 将大程序分解成小程序,以编译为例,gcc会调用cpp,cc1,cc2,as,ld
  • 使系统易于复用和扩展

并发创建新进程时的标识分配

程序可以调用函数fork()来创建一个新的进程

  • 操作系统需要分配一个新的并且唯一的进程ID
  • 在内核中,这个系统调用会运行
new_pid = next_pid++
  • 翻译成机器指令
LOAD next_pid Reg1
STORE Reg1 new_pid
INC Reg1
STORE Reg1 next_pid

两个进程并发执行时的预期结果(假定next_pid=100),一个进程得到的ID应该是100,另一个进程的ID应该是101,next_pid应该增加到102(但实际情况还是101,新进程分配标识中的可能错误)

原子操作(Atomic Operation)

原子操作是指一次不存在任何中断或失败的操作

  • 要么操作成功完成
  • 或者操作没有执行
  • 不会出现部分执行的状态

操作系统需要利用同步机制在并发执行的同时,保证一些操作是原子操作

现实生活中的同步问题

例如采购问题,如何避免重复购买?
在这里插入图片描述

家庭采购协调问题分析

如何保证家庭采购协调的成功和高效

  • 有人去买,需要采购时,有人去买面包
  • 最多只有一个人去买面包

可能的解决方法,在冰箱上设置一个锁和钥匙( lock&key),去买面包之前锁住冰箱并且拿走钥匙。但加锁导致的新问题,冰箱中还有其他食品时,别人无法取到。

方案一

使用便签来避免购买太多面包,购买之前留下一张便签,买完后移除该便签,别人看到便签时,就不去购买面包。
在这里插入图片描述
方案一分析

偶尔会购买太多面包

  • 检查面包和便签后,帖便签前,有其他人检查面包和便签

解决方案只是间歇性地失败

  • 问题难以调试
  • 必须考虑调度器所做的事情

在这里插入图片描述

方案二

先留便签,后检查面包和便签,会发生什么?不会有人买面包
在这里插入图片描述

在这里插入图片描述

方案三

为便签增加标记,以区别不同人的便签

  • 现在可在检查之前留便签
    在这里插入图片描述
    在这里插入图片描述
    会发生什么?可能导致没有人去买面包,每个人都认为另外一个去买面包。
方案四

两个人采用不同的处理流程
在这里插入图片描述

现在有效吗?枚举所有可能后,可以确认它是有效的

方案四分析

它有效,但太复杂,很难验证它的有效性

A和B的代码不同,每个进程的代码也会略有不同,如果进程更多,怎么办?

当A在等待时,它不能做其他事。忙等待(busy-waiting)

方案五

利用两个原子操作实现一个锁(lock)

Lock.Acquire()

  • 在锁被释放前一直等待,然后获得锁
  • 如果两个线程都在等待同ー个锁,并且同时发现锁被释放了,那么只有一个能够获得锁

Lock.Release()

  • 解锁并唤醒任何等待中的进程

基于原子锁的解决方案

在这里插入图片描述

进程的交互关系:相互感知程度

在这里插入图片描述

互斥 ( mutual exclusion ) 一个进程占用资源,其它进程不能使用

死锁(deadlock) 多个进程各占用部分资源,形成循环等待

饥饿(starvation) 其他进程可能轮流占用资源,一个进程一直得不到资源

临界区( critical section)

进程中访向临界资源的一段需要互斥执行的代码
在这里插入图片描述

进入区( entry section) 检查可否进入临界区的一段代码,如可进入,设置相应"正在访问临界区"标志

退出区( exit section) 清除“正在访问临界区"标志

剩余区( remainder section) 代码中的其余部分

临界区的访问规则

空闲则入:没有进程在临界区时,任何进程可进入

忙则等待:有进程在临界区时,其他进程均不能进入临界区

有限等待:等待进入临界区的进程不能无限期等待

让权等待(可选):不能进入临界区的进程,应释放CPU(如转换到阻塞状态)

临界区的实现方法

方法1:禁用硬件中断

没有中断,没有上下文切换,因此没有并发。硬件将中断处理延迟到中断被启用之后

进入临界区,禁止所有中断,并保存标志

离开临界区,使能所有中断,并恢复标志

现代计算机体系结构都提供指令来实现禁用中断

local_irq_save(unsigned long flags); 
critical section
local_irq_restore(unsigned long flags); 

缺点:禁用中断后,进程无法被停止,整个系统都会为此停下来,可能导致其他进程处于饥饿状态。
临界区可能很长,无法确定响应中断所需的时间(可能存在硬件影响)。
要小心使用

方法2:基于软件的解决方法

两个线程,T0和T1
线程 Ti 的代码
在这里插入图片描述
线程可通过共享一些共有变量来同步它们的行为

Peterson算法

满足线程Ti和Tj之间互斥的经典的基于软件的解决方法(1981年)

共享变量

int turn;  //表示该谁进入临界区
boolean flag[];   //表示进程是否准备好进入临界区

进入区代码

flag[i] = true;
turn = j;
while (flag[j] && turn ==j)

退出区代码

flag[i] = false;

Peterson算法实现

线程Ti 的代码
在这里插入图片描述

基于软件的解决方法的分析

  • 复杂,需要两个进程间的共享数据项
  • 需要忙等待,浪费CPU时间

方法3:更高级的抽象方法

硬件提供了一些同步原语,中断禁用,原子操作指令等

操作系统提供更高级的编程抽象来简化进程同步,例如:锁、信号量。用硬件原语来构建

锁(lock)

锁是一个抽象的数据结构

一个二进制变量(锁定/解锁)

  • Lock::Acquire() 锁被释放前一直等待,然后得到锁
  • Lock::Release() 释放锁,唤醒任何等待的进程

使用锁来控制临界区访问

原子操作指令

现代CPU体系结构都提供一些特殊的原子操作指令

测试和置位(Test-and-Set )指令

  • 从内存单元中读取值
  • 测试该值是否为1(然后返回真或假)
  • 内存单元值设置为1
    在这里插入图片描述
    交换指令(exchange) 交换内存中的两个值
    在这里插入图片描述

使用TS指令实现自旋锁(spinlock)

在这里插入图片描述

无忙等待锁

在这里插入图片描述

原子操作指令锁的特征

优点:
适用于单处理器或者共享主存的多处理器中任意数量的进程同步
简单并且容易证明
支持多临界区

缺点:
忙等待消耗处理器时间
可能导致饥饿,进程离开临界区时有多个等待进程的情况

死锁:
拥有临界区的低优先级进程
请求访问临界区的高优先级进程获得处理器并等待临界区

同步方法总结

锁是一种高级的同步抽象方法

  • 互斥可以使用锁来实现
  • 需要硬件支持

常用的三种同步实现方法

  • 禁用中断(仅限于单处理器)
  • 软件方法(复杂)
  • 原子操作指令(单处理器或多处理器均可)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gxhlh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值