并发性:互斥与同步
并发是所有问题的基础,也是操作系统设计的基础。并发包括很多设计问题,其中有进程通信、资源和竞争、多个进程活动的同步以及给进程分配处理器时间等。
感知程度 | 关系 | 一个进程对其它进程的影响 | 潜在的控制问题 |
---|---|---|---|
进程之间不知道对方的存在 | 竞争 | 一个进程的结果与另外一个无关;进程的执行时间可能会受到影响 | 互斥、死锁、饥饿 |
进程间接知道对方的存在 | 通过共享合作 | 一个进程的结果可能取决于从另一个进程获得的信息;进程的执行时间可能会受到影响 | 互斥、死锁、饥饿、数据一致性 |
进程直接知道对方的存在 | 通过通信合作 | 一个进程的结果可能取决于从另一个进程获得的信息;进程的执行时间可能会受到影响 | 死锁(可消耗资源)、饥饿 |
1. 互斥的要求
- 必须强制实施互斥:在与相同资源或共享对象的临界区有关的所有进程中,一次只允许一个进程进入临界区
- 一个在非临界区停止的进程不能干涉其他进程
- 绝不允许出现需要访问临界区的进程被无限延迟的情况,即不会出现死锁或者饥饿
- 对相关进程的执行速度和处理器的数量没有任何要求和限制
- 一个进程驻留在临界区中的时间必须是有限的
互斥的硬件支持:
- 终端禁用
- 专用机器指令
2. 信号量
一般常用的并发机制如下表:
机制 | 描述 |
---|---|
信号量 | 用于进程间传递信号的一个整数值。在信号量上只能进行三种原子操作(初始化、递减和增加)。递减操作是用于阻塞一个进程,递增操作是用于解除对一个进程的阻塞。信号量也称为计数信号量或一般信号量 |
二元信号量 | 只取值0或1的信号量 |
互斥量 | 类似于二元信号量。关键区别在于为其加锁(设定值为0)的进程和为其解锁(设定值为1)的进程必须为同一个进程 |
条件变量 | 一种数据类型,用于阻塞进程或线程,直到特定的条件为真 |
管程 | 一种编程语言结构,它在一个抽象数据类型中封装了变量、访问过程和初始化代码。管程的变量只能由自身的访问过程访问,每次只能有一个进程在其中执行。访问过程即临界区。管程可以有一个进程等待队列 |
事件标志 | 用作同步机制的一个内存字。应用程序代码可为标志中的每个位关联不同的事件。通过测试相关的一个或多个位,线程可以等待一个事件或多个事件。在全部所需要的位都被设定或至少有一个位被设定之前,线程会一直被阻塞 |
消息 | 两个进程交换信息的一种方法,也可用于同步 |
自旋锁 | 一种互斥锁,进程在一个无条件循环中执行,等待锁变量的值可用 |
2.1 消费者和生产者问题
- 有一个或多个生产者生产某种类型的数据,并放置在缓冲区中;
- 有一个消费者从缓冲区中取数据,每次取一项;
- 系统保证避免对缓冲区的重复操作,即在任何时候只有一个主体(生产者或消费者)可访问缓冲区;
- 当缓冲区已满时,生产者不会向其中添加数据;
- 当缓冲区为空时,消费者不会从中移走数据。
3. 管程
管程由一个或多个过程、一个初始化序列和局部数据组成的软件模块,其主要特点如下:
- 局部数据变量只能被管程的过程访问,任何外部过程都不能访问
- 一个进程通过调用管程的一个过程进入管程
- 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用
4. 消息传递
进程交互时,必须满足两个基本要求:同步和通信。为实施互斥,进程需要同步;为实现合作,进程需要交换信息。
两个进程间的通信隐含着某种同步的信息:只有当一个进程发送消息后,接受者才能接收到信息。
消息传递的实际功能通过一对原语实现:
send(destination, message)
receive(source, message)
1
2
2
寻址 send原语中哪里一个进程接受消息很重要。直接寻址是原语中包括目标进程的识别号。间接寻址时,消息不发送到特定的进程,而是发送到一个共享的数据结构,该结构由临时保存信息的队列组成,这些队列通畅被称为邮箱
。
5. 读者/写者问题
存在一个多进程共享的数据区,改数据区可以是一个文本或一块内存空间,甚至可以是一组寄存器;有些进程(reader)只能读取这个数据区中的数据,有些进程(writer)只能往数据区中写数据,此外,还必须满足以下条件:
- 任意数量的读进程可以同时读取这个文件;
- 一次只有一个写进程可以写文件;
- 若一个进程正在写文件,则禁止其他任何进程读取文件
5.1 读者优先
第一个试图读的读进程需要等待写进程不再访问共享数据区,当至少一个读进程在读时,随后的读进程无序等待,可以直接进入。
5.2 写者优先
在一个写进程声明想写时,不运行新的读进程进入。