同步互斥

第9章:同步互斥

标签(空格分隔): 操作系统


  • 背景
  • 现实生活中的同步问题
  • 临界区
  • 方法1:禁用硬件中断
  • 方法2:基于软件的解决方法
  • 方法3:更高级的抽象方法

9.1 背景

  • defs:同步互斥是操作系统当中协调进程之间动作和相互关系的一种机制。

并发进程的正确性

  • 独立进程:
    • 不和其他进程共享资源或状态
    • 确定性–输入状态决定结果
    • 可重现–能够重现起始条件
    • 调度顺序不重要
  • 并发进程:
    • 在多个进程间共享资源
    • 不确定性
    • 不可重现
  • 并发进程的正确性:
    • 执行进程是不确定性和不可重现的
    • 程序错误可能是间歇性发生的

进程并发执行的好处

  • 进程需要与计算机中的其他进程和设备进行协作
  • 好处1:共享资源
  • 好处2:加速
    • I/O操作和CPU计算可以重叠(并行)
    • 程序可划分成多个模块放在多个处理器上并行执行
  • 好处3:模块化
    • 将大程序分解成小程序
    • 使系统易于复用和扩展

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

  • 程序可以调用函数fork()来创建一个新的进程
    • 操作系统需要分配一个新的并且唯一的进程ID
    • 在内核中,这个系统调用会运行 new_pid = next_pid++
    • 翻译成机器指令
    • 两个进程并发执行时的预期结果(假定是 next_pid = 100)
      • 一个进程得到的ID应该是100
      • 另一个进程的ID应该是101
      • next_pid应该增加到102
  • 新进程标识中的可能错误:
    • 两个不同的新进程分配了一样的new_pid,且next_pid只增加了1

原子操作(Atomic Operation)

  • defs:原子操作是指一次不存在任何中断或失败的操作
    • 要么操作成功完成
    • 要么操作没有执行
    • 不会出现部分执行的状态
  • 操作系统需要利用同步机制在并发执行的同时,保证一些操作是原子操作

9.2 现实生活中的同步问题

  • 互斥(mutual exclusion)
    • 一个进程占用资源,其他进程不能使用,必须等待适应资源的进程释放
  • 死锁(deadlock)
    • 多个进程各占用部分资源,形成循环等待
  • 饥饿(starvation)
    • 其他进程可能轮流占用资源,一个进程一直得不到资源

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

相互感知的程度 交互关系 进程间的影响
相互不感知(完全不了解其他进程的存在) 独立 一个进程的操作催其他进程的结果无影响
间接感知(双方都与第三方交互,如共享资源) 通过共享进行协作 一个进程的结果依赖于共享资源的状态
直接感知(双方直接交互,如通信) 通过通信进行协作 一个进程的结果依赖于从其他进程获得的信息

9.3 临界区(critical section)

  • 临界区(critical section)
    • 进程访问临界资源的一段需要互斥执行的代码
  • 进入区(entry section)
    • 检查可否进入临界区的一段代码
    • 如可进入,设置相应“正在访问临界区”标志
  • 退出区(exit section)
    • 清除“正在访问临界区”状态
  • 剩余区(remainder section)
    • 代码中的其余部分

临界区访问规则

  • 空闲则入:
    • 没有进程在临界区时,任何进程可进入
  • 忙则等待:
    • 有进程在临界区时,其他进程均不能进入临界区
  • 有限等待:
    • 等待进入临界区的进程不能无限等待
  • 让权等待(可选):
    • 不能进入临界区的进程应释放CPU(如转换到阻塞状态)

临界区的实现方法

  • 禁用中断
  • 软件方法
  • 更高级的抽象方法

禁用中断

  • 其他进程无法打扰正在运行的进程,当前进程对临界区资源的访问也就不会有问题。但对系统的中断响应会有影响。
  • 在硬件还没有支持的时候,尝试用共享变量协调的方式来做,比较复杂
  • 借用操作系统的支持,来使应用提供同步的服务。由于引入管理者,进程间不对等协调

不同的临界区实现机制的比较

  • 性能:并发级别

9.4 禁用硬件中断

  • 没有中断,没有上下文切换,因此没有并发,整个系统由当前进程独占

    • 硬件将中断处理延迟到中断被启用之后
    • 现代计算机体系结构都提供指令来实现禁用中断

    • 进入临界区

    • 禁止所有中断,并保存标志

    • 离开临界区

    • 使能所有中断,并恢复标志

缺点

  • 禁用中断中,进程无法被停止

    • 如果进程执行出了问题,那整个系统都没有办法回来
    • 整个系统都会为此停下来
    • 可能导致其他进程处于饥饿状态
  • 临界区可能很长

    • 无法确定响应中断所需的时间(可能存在硬件影响)
  • 要小心使用

9.5 基于软件的同步方法

  • defs:在两个进程之间,通过共享变量的访问,来实现线程之间的同步
  • 线程可通过共享一些共有变量之间来同步它们的行为

第一次尝试

  • 共享变量
int turn = 0
turn == i//表示允许进入临界区的线程 
  • 线程Ti的代码
do {
    while (turn != i);//条件不成立就可以进入临界区
    critical section
    trun = j;
    remainder section
}  while (1);
  • 若turn不是i,则相当于我本身是i。允许另外一个线程进入临界区,i不允许进入临界区,这时它就一直等待,等待另一个线程把它改成i
  • 当turn变成i之后,它能进去执行临界区的代码,执行完毕退出后,在退出区,它把turn改成j,也就是另一个线程的ID
  • 满足“忙则等待”,但是有时不满足“空闲则入”
    • Ti不在临界区,Tj想要继续运行,但是必须等待Ti进入临界区后

第二次尝试

  • 共享变量
int flag[2];
flag[0] = flag[1] = 0;
flag[i] == 1;//表示线程Ti是否在临界区
  • 线程Ti的代码
do {
    while (flag[j] == 1);
    flag[i] = 1;
    critical section
    flag[i] = 0;
    remainder section 
} while (1);
  • 如果flag[j]不是1,则另一个线程不在临界区,这时把自己的标识改成1,进入临界区,出来了把自己的标识置为0
  • 不满足“忙则等待”

第三次尝试

  • 共享变量
int flag[2];
flag[0] = flag[1] = 0;
flag[i] == 1;//表示线程Ti想要进入临界区
  • 线程Ti的代码
do {
    flag[i] = 1;
    while (flag[j] == 1);
    critical section
    flag[i] = 0;
    remainder section
} whlie (1);
  • 满足“忙则等待”,但是不满足“空闲则入”

Peterson算法

  • 满足线程Ti和Tj之间互斥的基于软件的解决方法
  • 共享变量
int turn;
boolean flag[];//表示进程是否准备好进入临界区
  • 进入区代码
flag[i] = true;
turn = j;
while (flag[j] && turn ==j) //两个条件只要一个不满足就能进去
  • 退出区代码
flag[i] = false;
  • 线程Ti的代码
do {
    flag[i] = true;
    turn = j;
    while (flag[j] && turn == j);
        critical section
    flag[i] = false;
        remainder section
} while (1);

Dekkers算法

  • 线程Ti的代码
flag[0] := false; 
flag[1] := false;
turn := 0;//orl

do {
    flag[i] = true;
    while flag[j] == true {
        if turn != i {
            flag[i] := false
            while turn != i{}
                flag[i] := true
        }
    }
    critical section
    turn := j
    flag[i] = false;
    remainder section
} while (true);

N线程的软件方法(Eisenberg 和 McGuire)

turn i-1 i i+1 i+2 n-1 0
处理循环 处理循环 处理循环 处理循环 处理循环 处理循环 处理循环 处理循环 处理循环
  • 线程Ti要等待从turn到i-1的线程都退出临界区后访问临界区
  • 线程Ti退出时,把turn改成下一个请求进程

  • 特点:

    • 复杂:

      • 需要两个进程间的共享数据项
    • 需要忙等待

      • 浪费CPU时间

9.6 更高级的抽象方法

  • 硬件提供了一些同步原语

    • 中断禁用,院子操作指令等
  • 操作系统提供更高级的编程抽象来简化进程同步

    • 例如:锁、信号量
    • 用硬件原语来构建

锁(lock)

  • 锁是一个抽象的数据结构

    • 一个二进制变量(锁定/解锁)
    • Lock :: Acquire()

      • 锁被释放前一直等待,然后得到锁
    • Lock :: Release()

      • 释放锁,唤醒任何等待的进程
  • 使用锁来控制临界区访问

lock_next_pid -> Acquire();
new_pid = next_pid++;
lock_next_pid -> Release();

原子操作指令

  • 现代CPU体系结构都提供一些特殊的原子操作指令
  • 测试和置位(Test-and-Set)指令

    • 从内存单元中读取值
    • 测试该值是否为1(然后返回真或假)
    • 内存单元值设置为1
  • 交换指令(exchange)

    • 交换内存中的两个值

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

class Lock {
    int value = 0;//初始化锁里的初值为0
}
Lock :: Acquire() {
    while (test-and-setvalue));//spin
    //构造它的请求操作原语,即用TS指令去把value的值读出来,并且往里写1。
    //如果是1,则这个操作就相当于是个检查,如果是0,则设为1,循环结束。
}
  • 如果锁被释放,那么TS指令读取0并将值设置1

    • 锁被设置为忙并且需要等待完成
  • 如果锁处于忙状态,那么TS指令读取1并将值设置为1

    • 不改变锁的状态并且需要循环
Lock :: Release() {
    value = 0;
}
  • 特点:
    • 线程在等待的时候消耗CPU时间

无忙等待锁

  • 忙等待
Lock :: Acquire() {
    while (test-and-setvalue));//spin
}
Lock :: Release() {
    value = 0;
}
  • 无忙等待
class Lock {
    int value = 0;
    WaitQueue q;//在锁的数据结构里加上一个等待队列,即等待这个锁的相应的进程所排的队列
}
Lock :: Acquire() {
    while (test-and-setvalue));{
        and this TCB to wait queue q;
        schedule();

    }
}
  • 若查询一次之后,里面是1,就将当前线程放到等待队列,同时执行调度程序,此时其他程序可以继续执行
  • 一旦切换回来,轮到该线程再运行时,即有释放锁操作,将该线程从等待状态变成就绪状态,此时再去查,若此时它的状态是解锁的状态,则请求完成,进入临界区
Lock :: Release() {
    value = 0;//表示释放锁
    remove one thread t from q;
    wakeup(t);//将线程从等待队列放到就绪队列。等待时,就处于放弃CPU使用权的状态,实现了让权等待
}

原子操作指令锁的特征

  • 优点

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

    • 忙等待消耗处理器时间
    • 可能导致饥饿
      • 进程离开临界区时有多个等待进程的情况
      • 并没有做到按先来后到的顺序来使用资源,因为在锁的请求操作当中,放到就绪队列之后会再去检查。若在检查的时候资源出于空闲状态,那就申请到了。回来的时候,实际上各个线程把就绪队列排定的顺序并不见得是申请锁的顺序
    • 拥有临界区的低优先级进程
    • 请求访问临界区的高优先级进程获得处理器并等待临界区
      • 低优先级等待CPU,高优先级等待临界区资源

同步方法总结

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

    • 互斥可使用锁来实现
    • 需要硬件支持
  • 常用的三种同步实现方法

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

猜你喜欢

转载自blog.csdn.net/weixin_42061048/article/details/80406519