四. 同步互斥机制
同步互斥机制
进程并发执行: 是操作系统设计的基础, 但同时也是所有问题产生的基础
进程的特征:
并发: 进程的执行时间断性的, 进程的相对执行速度不可预测
共享: 进程/线程之间的制约性
不确定性: 进程执行的结果与其执行的相对速度有关, 是不确定的
与时间有关的错误例子1:

与时间有关的错误例子2:


进程互斥
竞争条件(race condition)
竞争条件就是说两个或多个进程, 在读写某些共享数据的时候, 最后的结果取决于进程运行的一个精确的时序(时间序列), 就是它一跟时间是相关的 那么这就是带来了竞争条件 所以竞争条件呢,是由于有这样一个共享的资源,共享的数据,而多个进程 都对这个数据进行相应的操作带来的 那么这样就产生了这样一个概念
进程互斥(mutual exclusive)
由于各进程要求使用共享资源(变量、文件等), 而这些资源需要排他性使用(在某个进程使用时, 其他进程不能使用), 各进程之间竞争使用这些资源 —— 这一关系称为进程互斥
临界资源(critical resource): 系统中某些资源一次只允许一个进程使用,称这 样的资源为临界资源或互斥资源或共享变量
临界区(互斥区)(critical section(region)): 各个进程中对某个临界资源(共享变量)实施操作的程序片段. (这些共享同一变量的程序片段, 分散在不同的进程里头, 它们的共同的特点, 是对同一个共享变量进行一些操作. 这一段代码和另外一个进程的这一段代码, 互为临界区,互为互斥区)
临界区(互斥区)的使用原则
没有进程在临界区时, 想进入临界区的进程可进入
不允许两个进程同时处于其临界区中
临界区外运行的进程不得阻塞其他进程进入临界区
不得使进程无限期等待进入临界区

实现进程互斥的方案:
软件方案: Dekker解法, Peterson解法
硬件方案: 屏蔽中断, TSL(XCHG)指令
软件方案:
1. Dekker解法

pturn, qturn: 初值为false
P进入临界区的条件: pturn∧notqturn
Q进入临界区的条件: not pturn∧ qturn
2. Peterson解法

process 0 进入enter_rigion, 走走走, turn = process 0.
时间片到了换process 1 走走走, 把turnget覆盖了
所以现在turn=process 1, 时间片到又切回process 0.
process 0 进入while循环, 由于turn被覆盖不满足第一个条件,
process 0离开while循环, 进入临界区.
而切换回process 1, 由于两个条件都满足while循环, 就一直循环,
直到process 0 离开临界区.
硬件方案: 硬件解决方案就是用特殊的一些指令来达到保护临界区的目的
1. 中断屏蔽方法
进去临界区前, 执行”关中断”指令
临界区操作
出了临界区之后, 执行”开中断”指令
特点:
- 简单,高效
- 代价高, 限制CPU并发能力(如果临界区范围大, 那么有些指令可能无法并发执行)
- 不适用与多处理器
- 适用于操作系统本身, 不适于用户进程(用户程序不允许使用该特权指令)
2. “测试并加锁”指令
TSL指令: test and set lock

3. “交换”指令
XCHG指令: exchange

概念: 忙等待(busy waiting): 上面解决方案的循环部分都是忙等待.
有个循环不断的来测试这个锁是不是打开了或者是测试这个条件是不是成立. 它是在 CPU 上一直在做测试, 这种测试称之为忙等待.
进程在得到临界区访问权之前, 持续测试而不做其他事情, 浪费了CPU的周期, 在单 CPU 的系统中, 会一直占着 CPU, 其它进程上不了 CPU, 没办法把相应的资源还回来的, 所以不适合单处理. 但是若为多处理器, 则会形成一个自旋锁 (spin lock).
进程同步(协作关系)
进程同步(synchronization): 指系统中多个进程中发生的事件存在某种时序关系, 需要相互合作,共同完成一项任务.
具体地说, 一个进程运行到某一点时, 要求另一伙伴进程为它提供消息, 在未获得消息之前, 该进程进入阻塞态, 获得消息后被唤醒进入就绪态.
例子:


场景发生, 即错误产生, 无法解决生产者/消费者问题
例子2:

在这里, 输入程序为生产者, 作业调度程序为消费者. 如果没有作业进入输入井, 那么调度程序要睡眠.
作业控制进程实和输出进程, 也是一对生产者和消费者.
同步例子3:

P1一直到P8, 这8个进程必须满足这样一个关系: P1全部执行完了P2,P3,P6才能执行. 然后P3执行完了, P4,P5才能执行. P2,P4,P5,P6都执行完了, 才能够P7或者P8它们两有一个执行. 当P1到P8这8个进程, 同时并发在系统中执行的过程中, 谁都可能先上CPU, 要满足这个同步关系, 就不能够在其他都没执行完的时候就执行P8.
进程同步机制
信号量(semaphore): 在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用.
它是个特殊变量, 用于进程间传递信息的一个整数值
定义:
struc semaphore{ int count; queueType queue; }
(以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用)
信号量和互斥量: https://www.zhihu.com/question/47704079
对信号量可以实施的操作: 初始化, P(又称down, semWait)和V(又称up, semSignal)

P、V操作为原语操作(primitive or atomic action)
二值信号量可以实现互斥, 多值信号量可以实现同步.
互斥:进程(同类的)间相互排斥的使用临界资源的现象,就叫进程互斥.
同步:进程(不同类的)之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系. 进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待. 具有同步关系的一组并发进程相互发送的信息称为消息或事件. 简单的说就是不同类的进程相互合作使用临界资源.
P、V操作步骤:
- 分析并发进程的关键活动,划定临界区
- 设置信号量 mutex,初值为1
- 在临界区前实施 P(mutex)
- 在临界区之后实施 V(mutex)

假设 P1 先上 CPU 那么它在做 P 操作的时候把 mutex 减1, mutex 现在是 0. 那么 0 不小于 0,所以 P1 进程就可以进入临界区. 如果 P1 进程在临界区期间被中断了, 那么 P2 进程正好上CPU, 它也想进临界区, 它也要做 P(mutex), 而 mutex 刚才是 0, 现在再减 1 就变成负 1 了, 因此, 根据定义 P2 进程就等在 mutex 的这个队列上. 让出 CPU 之后, 假设 P3 进程又上 CPU 了, 它也要进临界区, 又把 mutex 又减去了一次 1变成了负 2 . 因此, P3 进程也等在这个信号量上, 等在 P2 的后面, 然后让出了 CPU. 假设 P1 又上 CPU 了 然后它就在临界区里头完成了它的工作, 然后出临界区. 出临界区它执行了一个 V(mutex), mutex加 1, 刚才是负 2,加 1 变成负 1,那这个时候 信号量的值呢还是小于等于 0,因此这个 V 操作就会到队列里头找到一个进程 P2 把它送到了就绪队列,然后 P1 接着做别的事情. 如果待会 P2 上 CPU 了, 那么它就 下一个就进入临界区了. 因为 P 操作已经执行完了,所以它接着就进临界区 进临界区,当它出临界区又做一次 V 操作,mutex 就变成 0 了 变成 0 了之后还是小于等于 0 ,所以呢 V 操作就会把 队列里等的 P3 让它进入就绪,就是这样一个过程

读者与写者的问题
有一块共享的数据区域,然后有读者和写者前来分别读取数据,或者写入数据,要求如下:
- 读者只读出数据,写者只写入数据,不能混淆
- 任意多个读者可以同时读取数据(类似于内存的存取)
- 每次只能有一个写者去写入数据
- 一个写者正在写入数据的同时,其他的任意读者或者写者不能读取或者写入数据
三类情况:读者优先,公平情况,写者优先
一. 读者优先算法;
读者先来读取数据(其他的读者也可以来读取数据),此时写者等待,也就是说读者可以插写者的队,这是读者优先的关键点,只有当读者为0,写者才能来写入数据。
- 写者要有一个互斥信号量 writeMutex=1,因为写者一次只能一个来写数据
- 对读者要有一个记录数目的 int 变量,readcount=0,一个互斥信号量readMutex = 1,保证多个读者来的时候,能似的 readcount 互斥的变化,也就是不被混乱的计数。

//读者优先 int readcount = 0; semaphore x = 1, wsem = 1; void reader() { while(true){ semWait(x); readcount ++ ; if(readcount == 1) semWait(wsem); semSignal(x); READUNIT();//读数据 semWait(x); readcount --; if(readcount == 0) semSignal(wsem); semSignal(x); } } void writer() { while(true){ semWait(wsem); WRITEUNIT();//写数据 semSignal(wsem); } } 信号量x保证readCount被正确更新.wsem用于文件读写的互斥. 分析: 1:对于写进程. 如果一个写执行semWait(wsem),然后写WRITEUNIT();对于其他写进程明显回被阻塞在wsem上.而对于读进程,第1个读进if(readcount == 1) emWait(wsem);也会被阻塞在 wsem上.而后续的读进程会被阻塞在第一个semWait(x);上. 2:如果多个读进程要读文件,那么第一个读进程在if(readcount == 1) emWait(wsem);时如果有写进程在写,转1.如果无写进程,那么wsem-1=0; 这样就保证了读的时候写进程会被阻塞在wsem上. 而后续的读进程可以直接读文件.当所有读进程都读完时if(readcount==0)semSignal(wsem); 阻塞在wsem上的写进程就可以写了. 综上所述: (1)只有当所有读进程都读完了,写进程才可以写. (2)或者一个写进程写完后,semSignal(wsem),可能会被读进程抢占if(readcount == 1) emWait(wsem);而写进程无法抢占读进程. 所以为读优先;
//写者优先 int readcount=0 , writecount=0; semaphore x = 1, y = 1, z = 1, wsem = 1 , rsem = 1; void reader() { while(true){ semWait(z);//z信号用来保证阻塞在rsem信号中排队的读者至多只有一个。其余的阻塞在z上。 semWait(rsem); semWait(x);//保证下面3句操作的原子性 readcount ++; if(readcount == i) semWait(wsem); semSignal(x); semSignal(rsem);//写者抢占访问权的时机! semSignal(z); READUNIT(); semWait(x); readcount --; if(readcount == 0) semSignal(wsem); semSignal(x); } } void writer() { while(true){ semWait(y); writecount ++; if(writecount == 1) semWait(rsem); semSignal(y); semWait(wsem); WRITEUNIT(); semSignal(wsem); semWait(y); writecount --; if(writecount == 0) semSignal(rsem); semSignal(y); } } 信号量rsem:当至少有一个写进程准备访问数据区时,用于禁止所有的读进程. 变量writecount:控制rsem的设置. 信号量y:控制writecount的更新. 过程分析: 1:对于写进程P,如果P是第一个写进程,那么semWait(rsem),如果 rsem=1,即没有读进程,则后续读进程会阻塞rsem上,直到所有的写进程都写完.,if(writecount == 0)semSignal(rsem);读进程才可以读.注意,写文件时wsem仍然保持着互斥,每次只有一个写进程可以写. 如果此时有读进程在读,转2. 2:对于读进程P,如果有写进程,则读进程会被阻塞在rsem上. 如果没有写进程在写,那么第一个写进程if(writecount == 1)semWait(rsem),被阻塞在rsem上,后边的写进程被阻塞在y上semWait(y);. 使得写进程只有等到P执行完后if(readcount == 0) semSignal(wsem);才可以执行,转1. 综上所述: 由于在有多个读进程在读,又有写进程要写时,写进程可以抢占机会进行写.而当多个写进程时,只有所有写进程运行完,读进程才可以读.所以是 写优先
公平竞争: 1.优先级相同。 2.写者、读者互斥访问。 3.只能有一个写者访问临界区。 4.可以有多个读者同时访问临界资源。 具体实现: 1.设置file信号量实现对临界资源的互斥访问。 2.设置计数器readCount实现多个读者访问临界资源,通过设置信号量readCountSignal实现对readCount计数器的互斥访问。 3.设置信号量keySignal实现读者和写者的公平竞争(令牌)。 4.设置信号量OneSignal实现只有读者队列或写者阻塞在keySignal(对令牌资源的访问控制)。 [cpp] view plain copy /* 读者队列初始值为0,其他资源初始值为1*/ int readCount = 0; semaphore keySignal = 1; semaphore OneSignal = 1; semaphore readCountSignal = 1; reader() { while(true) { wait(keySignal); //申请令牌 wait(readCountSignal); //申请计数器资源 if(!readCount) //为零则申请文件资源 wait(fileSrc); readCount++; signal(readCountSignal); //释放计数器资源 signal(keySignale); //释放令牌 ... perform read operation //执行临界区代码 ... wait(readCountSignal); //申请计数器资源 readCount--; if(!readCount) //为零则释放文件资源 signal(fileSrc); signal(readCountSignal); //释放读者计数器资源 } } writer() { while(true) { wait(OneSignal); //申请令牌资源 wait(keySignal); //申请令牌 wait(fileSrc); //申请文件资源 ... perform write operation //执行临界区代码 ... signal(fileSrc); //释放文件资源 signal(keysignal); //释放令牌 signal(OneSignal); //释放令牌资源 } }
https://blog.csdn.net/c1194758555/article/details/52805918
管程
http://blog.csdn.net/baidu_20363843/article/details/69944492
https://www.zhihu.com/question/30641734/answer/105402533
PTHREAD中的同步机制
通过互斥量, 提供相应的操作来保护临界区
通过条件变量, 实现同步




进程间通信IPC
信号量和管程只能传递简单信息, 不能传递大量信息
管程不适合多处理器情况
进程通信机制: 消息传递 – send&receive原语
适用于:分布式系统、基于共享内存的多处理机系统、单处理机系统
作用: 可以解决进程间的同步/互斥问题、通信问题
基本通信方式:
- 消息传递
- 共享内存
- 管道
- 套接字
- 远程过程调用






浙公网安备 33010602011771号