1. 背景
同步互斥是操作系统协调进程之间动作和相互关系中的机制。互斥是指当一个进程在临界区访问共享资源时,其他进程不能进入该临界区访问任何共享资源。为实施互斥,进程之间就要采用同步机制。
- 并发进程的正确性
- 进程并发运行的好处
共享资源
加速
:I/O操作和CPU计算可以重叠,程序可分为多个模块在多个处理器上并行执行
模块化
:将大程序分解成小程序,使系统易于复用和扩展。 - 原子性操作
原子性操作时操作系统硬件提供的操作,它保证一些执行语句必须是原子操作。
2. 同步问题的一个例子
本节用一个简单的生活问题来理解同步。(具体分析过程详见视频:同步问题)
-
下图中,如果A和B的按照图中的顺序执行的话,就会出现面包重复购买的情况
首先来分析下这个问题:
-
解决方案
利用两个原子操作来实现一个锁(lock)Lock.Acquire()
:在锁被释放之前一直等待,然后获得锁,如果两个进程都在等待同一个锁,并且同时发现锁被释放了,那么只有一个获得锁,另一个仍处于等待
Lock.Release()
:解锁并唤醒任何等待中的进程 -
进程的交互关系
3. 同步问题的初步解决方案
临界区
:进程中访问临界资源的一段需要互斥执行的代码(也就是说有一个进程在访问临界区代码,另一个进程就不能访问该代码)
进入区
:检查可否进入临界区的一段代码,如可进入,则设置相应“正在访问临界区”标志‘
退出区
:清楚“正在访问临界区标志”
剩余区
:代码的其余部分
临界区访问规则
空闲则如
:没有进程在临界区,则任何进程可以进入
忙则等待
:有进程在临界区,其他进程均不能进入临界区
有限等待
:等待进入临界区的进程不能无限制等待
让权等待
(可选):不能进入临界区的进程,应释放CPU(比如转换到阻塞状态)
1. 方法一 禁用硬件中断
也就是禁止硬件中断的响应,相当于把中断使能关掉了。
2. 方法二 基于软件的同步办法
该方法就是通过进程共享一些共有变量来实现线程的同步。该方法的特征一是复杂(需要两个进程间的共享数据项),二是需要忙等待,浪费CPU时间。
-
示例
-
Peterson算法
第一种情况:另外一个进程Tj没有申请进入,则此时flag[j]为false,Ti可以正常进入临界区
第二种情乱:Tj申请进入,flag[j]为true,则先写turn语句的会进入临界区。比如说是Ti先写turn=j,则此时Ti 进不去(while两个条件都满足,会一直循环),然后Tj写turn(turn=i)后,Ti就会立马进去了,Ti运行完临界区代码后,flag[i]=false,则Tj就可以进去了。 -
Dekkers算法
3. 方法三 更高级的抽象方法
硬件提供了一些同步原语,比方说中断禁用、原子操作指令等,操作系统提供了更高级的编程抽象来简化进程的同步,比方说锁
、信号量
(这两个都是用硬件原语来构建的)
-
锁
锁是一个抽象的数据结构,它由一个二进制变量(锁定/解锁)和两个操作原语组成。它是基于原子操作指令来实现的Lock.Acquire()
:在锁被释放之前一直等待,然后获得锁,如果两个进程都在等待同一个锁,并且同时发现锁被释放了,那么只有一个获得锁,另一个仍处于等待
Lock.Release()
:解锁并唤醒任何等待中的进程 -
原子操作指令
CPU体系结构中提供了一些特殊的原子操作指令,这些指令把若干个操作合成一个原子操作,保证这些操作之间不会部分执行的状态。- TS指令
- 交换指令(交换内存中的两个值)
- TS指令
-
使用TS指令实现自旋锁
-
原子操作指令锁的特征
-
同步方法总结