同步互斥

独立程序

1、不和其他程序共享资源

2、输入状态决定结果,具有确定性

3、可重现起始条件

4、调度顺序不重要

 

并发进程

1、多个进程间有资源共享,可能会因为不同顺序出现相互的干扰

2、不确定性

3、不可重现

4、不确定性、不可重现导致 bug 是间歇性发生的

 

进程 / 线程合作优点

1、共享资源

2、加速:IO 操作、计算可以重叠,多处理器将程序分成多个部分并行执行

3、模块化:将大程序分解成小程序,使系统易于扩展

 

竞态条件

1、系统缺陷:结果依赖于并发执行或者事件的顺序 / 时间

2、避免竞态:原子操作(Atomic Operation):指一次不存在任何中断或者失败的操作,要么执行完成,要么没有执行,不会中途停止、部分执行

 

临界区

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

1、同步问题的解决方案主要基于间接感知的部分,其中主要会有如下三种状态

(1)互斥(mutual exclusion):一个进程占用资源,其他进程无法使用

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

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

2、最基本的状态称为互斥,是一个良好的原子操作的思路

3、临界区:互斥资源存放的位置,其上资源同时只允许一个进程的访问,临界区附近的代码结构如下:

(1)进入区:检查是否可以进入临界区的一段代码,如果可以进入,则设定“正在进入临界区”标志

(2)临界区:进程中访问临界资源的一段需要互斥执行的代码

(3)退出区:清除“正在访问临界区”标志

(4)剩余区:跟同步互斥无关的代码

4、临界区的访问规则,必须满足前三条规则

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

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

(3)有限等待:等待访问临界区的进程不能无限等待

(4)让权等待 / 无忙等待(可选):等待访问临界区的进程,访问之前会被挂起

 

实现方案

1、禁用中断

2、软件方法

3、高级抽象

 

禁用中断

1、禁止硬件中断响应,不会发生上下文切换,因此没有并发

(1)硬件将中断处理延迟到中断启用之后

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

2、进入临界区,禁用中断;离开临界区,开启中断

3、缺点

(1)禁用中断之后,进程无法停止,整个系统都会为此停下来,可能导致其他进程处于饥饿状态

(2)临界区可能很长,无法确定中断响应时间,可能存在硬件影响

(3)多个 CPU 情况下,CPU 只能禁用自身的中断,无法禁用其他 CPU 中断,无法解决同步互斥,即只能用于单个 CPU

 

软件方法

1、设置一些全局的共享变量,来标识两个线程对互斥资源的占用情况,从而实现同步

2、Peterson 算法

(1)复杂,需要两个进程间的共享数据项

(2)需要忙等待,浪费 CPU 时间

3、Dekker 算法

(1)针对双线程

(2)绝大多数的线程都会卡在里层循环,只有当前线程执行完之后释放资源,才将下一个 turn 轮到的线程唤醒

4、n 线程的 Eisenberg 和 McGuire 算法

(1)可以实现有效的轮流

(2)但多线程、多临界区的 debug 会变得更加困难

5、Bakery 算法

(1)n 个进程的临界区

(2)进入临界区之前,进程接收一个数字

(3)得到的教字最小的进入临界区

(4)如果进程 Pi 和 Pj 收到相同的数字,如果 i < j,Pi 先进入临界区,否则 Pj 先进入临界区

(5)编号方案总是按照枚举的增加顺序生成数字

 

高级抽象

1、锁:数据结构

(1)二进制状态:锁定、解锁

(2)主要 API

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

Lock::Release():释放锁,唤醒任何等待的进程

(3)使用锁封装相关的互斥操作,控制临界区访问

2、原子操作指令

(1)通过特殊的内存访问电路

(2)针对单处理器和多处理器

(3)Test-and-Set(测试和置位):从内存中读取值,测试该值是否为 1,然后返回 true 或 false,内存值设置为1

(4)Exchange(交换):交换内存中的两个值

4、Test-and-Set 实现 Lock::Acquire()

(1)如果锁被释放,即 test-and-set 读取 0,并将值设置为 1,锁被设置为忙并且需要等待完成

(2)如果锁处于忙状态,即 test-and-set 读取 1,并将值设置为 1,不改变锁的状态并且继续循环(自旋锁)

class Lock {
    int value = 0;
}

Lock::Acquire() {
    while (test-and-set());//spin
}

Lock::Release() {
	value = 0;
}

(3)增加实现无忙等待

class Lock {
    int value = 0;
    WaitQueue q;
}

Lock::Acquire() {
    while (test-and-set()) {
        add this TCB to wait queue q;
		schedule();//执行调度算法
    }
}

Lock::Release() {
	value = 0;
    remove one thread t from q;
	wakeup(t); 
}

5、Exchange 实现

int key;
do {
    key = 1;
    while (key == 1) {
        exchange(lock,key);
    }
    //critical section 
    lock=0;
    //remainder section
}

6、优点

(1)适用于单处理器,或共享主存的多处理器中任意数量的进程

(2)简单并且容易证明

(3)可以用于支持多临界区

7、缺点

(1)忙等待消耗处理器时间

(2)当进程离开临界区,并且多个进程在等待时,可能会导致饥饿

(3)可能会导致死锁:一个低优先级进程拥有临界区(拥有独占资源),一个高优先级进程拥有 CPU,低优先级无法释放锁,高优先级等待临界区

posted @   半条咸鱼  阅读(142)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示