进程的前三个部分 (进程的基本概念, 进程控制, 线程) 请阅读 操作系统 - 4 - 进程管理(一)
四, 进程同步
概念: 进程同步的主要任务是使并发执行的各进程之间能有效的共享资源和相互合作, 从而使程序的执行具有可再现性.
1 进程同步的基本概念
(1)进程之间的两种制约关系: 间接制约关系: 系统资源竞争, 进程间彼此无关
直接制约关系: 进程间合作, 彼此相关
(2)资源竞争需解决的两个问题: 死锁 (Deadlock) 问题, 饥饿 (Starvation) 问题(既要解决饥饿问题, 又要解决死锁问题.)
(3)进程互斥: 概念: 进程互斥指若干进程要使用同一共享资源时, 任何时刻最多允许一个进程使用, 其他进程必须等待, 直到占有资源的进程释放该资源.
作用: 进程互斥是解决进程间竞争关系 (间接制约关系) 的手段.
(4)进程合作: 概念: 进程合作是指某些进程为完成同一任务需要分工协作.
作用: 进程合作是解决进程间协作关系 (直接制约关系) 的手段.
(5)进程合作与进程互斥的区别: 进程同步指两个以上进程基于某个条件来协调它们的活动. 一个进程的执行依赖于协作进程的消息或信号,
当一个进程没有得到来自于协作进程的消息或信号时需等待, 直到消息或信号到达才被唤醒.
进程互斥是一种特殊的进程同步关系, 即依次使用互斥共享资源, 是对进程使用资源次序上的一种协调.
(6)临界资源(Critical Resource/CR): 一次仅允许一个进程访问的资源. 如: 进程 P1,P2 共享一台打印机, 若让它们交替使用则得到的结果肯定是不可理解的.
临界资源可能是硬件, 也可能是软件: 变量, 数据, 表格, 队列等.
并发进程对临界资源的访问必须作某种限制, 否则就可能出现与时间有关的错误, 如: 联网售票.
(7)临界区(Critical Section/CS): 临界段, 在每个程序中, 访问临界资源的那段程序.
与同一变量有关的临界区分散在各进程的程序段中, 而各进程的执行速度不可预知.
如果能保证进程在临界区执行时, 不让另一个进程进入临界区, 即各进程对共享变量的访问是互斥的, 就不会造成与时间有关的错误.
与时间有关的错误: 一飞机订票系统, 两个终端, 运行 T1,T2 进程
- T1 : T2:
- ... ...
- Read(x); Read(x);
- if x>=1 then if x>=1 then
- x:=x-1; x:=x-1;
- write(x); write(x);
- ... ...
注意: 临界区是对某一临界资源而言的, 对于不同临界资源的临界区, 它们之间不存在互斥.
如程序段 A,B 有关于变量 X 的临界区, 而 C,D 有关于变量 Y 的临界区,
那么, A,B 之间需要互斥执行, C,D 之间也要互斥执行, 而 A 与 C,B 与 D 之间不用互斥执行.
临界区的调度原则: 一次至多允许一个进程进入临界区内
一个进程不能无限地停留在临界区内
一个进程不能无限地等待进入临界区
(8)同步机制应遵循的规则: 空闲让进, 忙则等待, 有限等待, 让权等待
用软件方法解决进程互斥问题:(设两个进程 Pi,Pj 使用临界资源.)
算法一分析: 该算法规定了进程必须轮流执行; 若某进程不需要进入 CS, 而另一进程要求连续进入 CS, 该算法不能解决; 破坏了 "空闲让进" 的准则;
算法二分析: 该算法解决了算法一 "空闲让进" 的问题;
该算法中, 若多个进程同时要求进入 CS 时, 都发现对方进程的标志为 "假", 会同时进入 CS, 这样, 破坏了 "忙则等待" 的准则;
算法三分析: 该算法解决了算法二 "忙则等待" 的问题;
该算法中, 若多个进程同时要求进入 CS 时, 都将自己的标志设为 "真", 这样, 互相谦让, 谁也不会进入 CS. 这样, 破坏了 "空闲让进" 的准则;
算法四分析: 满足空闲让进, 忙则等待, 有限等待, 让权等待四个规则
生产者消费者问题:
问题分析: 利用一个数组表示具有 n 个缓冲区的循环缓冲池;
用输入指针 in 指示下一个可投放产品的缓冲区, 每当生产者进程生产并投放一个产品, 输入指针加 1 (in:=(in+1) mod n);
用指针 out 指示下一个可从中获取产品的缓冲区, 每当消费者进程取出一个产品, 输出指针加 1 (out:=(out+1) mod n) ;
----------------------------------------------------------------------------------------------------------------
2 信号量机制
概念: 1965 年, 由荷兰学者 Dijkstra 提出, P,V 操作分别是荷兰语的 test (Proberen) 和 increment (Verhogen) .
信号量机制是一种卓有成效的进程同步机制. 经历整型信号量, 记录型信号量, 发展为 "信号量集" 机制.
P,V 操作是原语.
信号量的值除初始化外, 只能由 P,V 原语修改.(wait,signal)
型号量的分类: 信号量按其用途分为: 公用信号量, 私有信号量
信号量按其取值分为: 二元信号量, 一般信号量
(1)整型信号量: 定义为一个整型量, 由两个标准原子操作 wait(S)(P 操作)和 signal(S)(V 操作)来访问.
P(S) 或 wait(S): while S≤0 do no-op;
S:=S-1;
V(S) 或 signal(S): S:=S+1;
整型信号量出现 "忙等" 现象.
P(S) 或 wait(S): S:=S-1; S>0: 可用资源数量
if (S<0) block(); S<0:|S | 表示等待使用资源的进程数量
V(S) 或 signal(S): S:=S+1;
if (S<=0) wakeup();
利用整型信号量实现互斥:
(2)记录型信号量: 记录型信号量机制采取 "让权等待" 策略, 即当进程不能申请到资源时, 让出 CPU 使用权. 避免了整型信号量出现的 "忙等" 现象.
记录型信号量需要一个用于代表资源数目的整型变量 value, 一个用于链接所有等待进程的进程链表 queue.
数据结构定义如下: struct semaphore {
- int value;
- pointer_PCB queue;
- };
信号量说明: struct semaphore s;
P 操作: P(s){
- s.value = s.value -1 ;
- if (s.value <0){
该进程状态置为等待状态
将该进程的 PCB 插入相应的等待队列末尾 s.queue;
}
}
V 操作: V(s){
- s.value = s.value +1 ;
- if (s.value <= 0){
唤醒相应等待队列 s.queue 中等待的一个进程
改变其状态为就绪状态, 并将其插入就绪队列
}
}
进程对 CR 进行申请: begin
- repeat
- P(S);
- CS;
- V(S);
- remainder;
- until false;
- end
(3)AND 型信号量
AND 信号量是针对进程间共享多个资源;
例如: 两个进程 A,B 要求访问共享数据 D,E. 为 D,E 分别设置用于互斥的信号量 Dmutex,Emutex, 初值为 1.
- process A: process B:
- wait(Dmutex); wait(Emutex);
- wait(Emutex); wait(Dmutex);
此处出现死等现象
AND 同步机制的基本思想是: 将进程在整个运行过程中需要的所有资源, 一次性全部地分配给进程, 待进程使用完后再一起释放.
只要尚有一个资源未分配给进程, 其它所有可能为之分配的资源, 也不分配.
实现时在 wait 操作中, 增加一个 "AND" 条件, 故称为 AND 同步, 或同时 wait 操作(Swait).
-------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------
(4)信号量集
进程对信号量 Si 的测试值为 ti(表示信号量的判断条件, 要求 Si>= ti; 即当资源数量低于 ti 时, 便不予分配)
占用值为 di(表示资源的申请量, 即 Si=Si-di)
对应的 P,V 原语格式为:
- Swait(S1, t1, d1; ...; Sn, tn, dn);
- Ssignal(S1, d1; ...; Sn, dn);
一般 "信号量集" 可以用于各种情况的资源分配和释放, 几种特殊情况:
(1)Swait(S, d, d)表示每次申请 d 个资源, 当少于 d 个时, 便不分配
(2)Swait(S, 1, 1)表示记录型信号量或互斥信号量
(3)Swait(S, 1, 0)可作为一个可控开关(当 S1 时, 允许多个进程进入特定区域; 当 S=0 时, 禁止任何进程进入该特定区域)
(4)一般 "信号量集" 未必成对使用. 如: 一起申请, 但不一起释放!
五, 经典的进程同步问题(等我后续单独写博客)
1 生产者 / 消费者问题
2 读者 / 写者问题
3 哲学家进餐问题
六, 管程机制
引入管程机制的原因: 减少大量的同步操作分散在各个进程中.
解决的办法 -- 引入管程: 为每个可共享资源设立一个专门的管程, 来统一管理各进程对该资源的访问.
管程 (Monitor) 的定义: 一个管程包含一个数据结构和能为并发进程所执行 (在该数据结构上) 的一组操作, 这组操作能同步进程和改变管程中的数据.
管程的三个部分: 局限于管程内使用的共享变量的定义
对局限于管程的数据初始化
对该数据结构进行操作的一组过程
管程的特点:(1)局部数据变量只能被管程的过程访问, 任何外部过程都不能访问.
(2)一个进程通过调用管程的一个过程进入管程.
(3)任何时候, 只能有一个进程在管程中执行, 调用管程的任何其他进程都被挂起, 以等待管程变成可用的.(由编译器控制一次只能有一个进程调用管程)
(4)为了保证共享变量的数据一致性, 管程应互斥使用.
(5)管程通常是用于管理资源的, 因此管程中有进程等待队列和相应的等待和唤醒操作.
(6)在管程入口有一个等待队列, 称为入口等待队列. 当一个已进入管程的进程等待时, 就释放管程的互斥使用权;
当已进入管程的一个进程唤醒另一个进程时, 两者必须有一个退出或停止使用管程.
(7)在管程内部, 由于执行唤醒操作, 可能存在多个等待进程(等待使用管程), 称为紧急等待队列, 它的优先级高于入口等待队列.
描述: 一个进程进入管程之前要先申请, 一般由管程提供一个 enter()过程;
离开时释放使用权, 如果紧急等待队列不空, 则唤醒第一个等待者, 一般也由管程提供外部过程 leave().
管程内部有自己的等待机制. 管程可以说明一种特殊的条件型变量: var c:condition; 实际上是一个指针, 指向一个等待该条件的 PCB 队列.
条件变量实际上是区分阻塞的原因.
条件变量: 管程利用 wait 原语让进程等待, 等待的原因可用条件变量 condition 区别. 它们应置于 wait 和 signal 之前.
例如: var X:condition;X.signal 表示重新启动一个被阻塞的进程, 但如果没有被阻塞的进程, 则 X.signal 不产生任何后果.
利用管程解决生产者 - 消费者问题(待我后续发布博客)
七, 进程通信
1 概念
(1)所谓进程通信是指进程之间信息交换.
(2)P,V 操作实现的是进程之间的低级通信, 所以 P,V 为低级通信原语. 它只能传递简单的信号, 不能传递交换大量信息.
(3)若要在进程间传递大量信息可以用 Send, Receive 原语(高级通信原语).
(4)信号量机制作为同步工具成效显著, 但作为通信工具, 存在不足:
效率低. 生产者一次只能放一个产品 (消息) 到缓冲区, 消费者一次只能取一个产品(消息);
通信对用户不透明.
用户用低级通信工具不够方便, 因为数据结构设置, 传送, 进程互斥, 同步等都由程序员负责.
2 进程通信类型
高级通信: 用户可直接利用 OS 提供的一组通信命令高效传送大量数据. 分为:
(1)共享存储器系统(Shared-Memory System) : 进程间通过共享某些数据结构或共享存储区进行通信.
基于共享数据结构的通信方式: 多个进程通过公用某些数据结构交换信息. 如: 生产者 - 消费者问题中的有界缓冲区.
该方式低效. 因数据结构设置, 同步等由程序员负责.
基于共享存储区的通信方式: 高级通信, 在存储器中划出一块共享存储区, 进程在通信前, 向系统申请共享存储区中的一个分区,
并指定该分区的关键字, 若系统已经给其它进程分配了这样的分区, 则将该分区的描述符返回给申请者.
接着, 申请者把获得的共享存储分区连接到本进程上, 此后可读写该分区.
注: 以上两种方式的同步互斥都要由进程自己负责.
(2)消息传递系统(Message Passing System): 进程间的数据交换以格式化的消息为单位, 程序员利用系统的通信原语实现通信. 如: 网络中的报文.
操作系统隐藏了通信的实现细节, 简化了通信程序编制的复杂性, 因而得到广泛应用.
消息传递系统可分为:
直接通信(也称为消息缓冲通信): 发送进程直接把消息发送给接收者, 并将它挂在接收进程的消息缓冲队列上. 接收进程从消息缓冲队列中取得消息.
间接通信: 发送进程将消息发送到某种中间实体中 (信箱), 接收进程从(信箱) 中取得消息. 也称信箱通信. 在网络中称为电子邮件系统.
两种通信方式的主要区别: 前者需要两进程都存在, 后者不需要.
(3)管道 (Pipe) 通信 (共享文件方式): 管道是最初的 UNIX IPC 形式, 它能传送大量数据, 被广泛采用.
所谓管道, 是指用于连接一个读进程和一个写进程的文件, 称 pipe 文件.
向管道提供输入的进程 (称写进程), 以字符流的形式将大量数据送入管道, 而接受管道输出的进程(读进程) 可从管道中接收数据.
管道可以是单工的, 也可以是双工的.
管道分类: 无名管道只限于相同祖先的进程间使用, 是单向的. 比如: 父进程与子进程之间, 或同一父进程的两个子进程间.
有名管道可在任意两个进程间使用, 有名文件, 有函数创建, 并且创建进程即使终止了, 该管道仍存在, 是单向的, 半双工的.
管道与消息队列的区别: 管道中的消息是无界的
管道是外存的, 消息队列是内存的
管道机制必须提供以下三方面能力:
互斥: 一个进程读 / 写管道时, 另一个读 / 写进程必须等待.
同步: 写进程写入后, 只有等到读进程读走数据后, 才能再写; 同样, 读进程无数据读时, 只有等到写进程写入后, 被唤醒再读.
确定对方是否存在: 只有对方存在时才能通信.
3 消息传递通信的实现方法
消息通信分为直接通信和间接通信.
直接通信方式: 通信命令(原语):Send(Receiver,message);
Receive(Sender,message);
间接通信方式 (信箱方式): 信箱的创建与撤消. 创建者应给出信箱名, 属性(公用, 私用或共享) 等
信箱中消息的发送与接收: Send(mailbox,message);
Receive(mailbox,message);
信箱的种类(3 类)
私用信箱: 用户进程为自己创建的信箱, 是进程的一部分. 只有自己有权读, 其他进程只能向信箱发消息; 可采用单向链路实现.
公用信箱: 由操作系统创建, 所有系统核准进程都可使用; 采用双向通信链路实现.
共享信箱: 由某进程创建并指明可共享, 则拥有者和共享者可使用.
利用信箱通信时, 有四种关系: 1:1 发送和接收进程之间建立专用链路;
N:1 即客户 / 服务器;
1:N 发送进程可用广播方式向多个接收者发消息;
N:N 建立一个公共信箱, 多个进程都能向信箱中发消 息, 也能从信箱中取消息;
信箱使用规则: 若发送信件时信箱已满, 则发送进程被置为 "等信箱" 状态, 直到信箱有空时才被唤醒
若取信件时信箱中无信, 则接收进程被置为 "等信件" 状态, 直到有信件时才被唤醒
Send 实现: send(MailBox,M): 把信件 M 送到指定的信箱 MailBox 中
步骤: 查找指定信箱 MailBox ;
若信箱未满, 则把信件 M 送入信箱且唤醒 "等信件" 者;
若信箱已满, 置发送信件进程为 "等信箱" 状态;
Receive 实现: receive(MailBox,X): 从指定信箱 MailBox 中取出一封信, 存放到指定的地址 X 中
步骤: 查找指定信箱 MailBox ;
若信箱中有信, 则取出一封信存于 X 中且唤醒 "等信箱" 者;
若信箱中无信件, 置接收信件进程 "等信件" 状态;
来源: https://www.cnblogs.com/qinqin-me/p/12716115.html