|
| 1 | +## 1,进程同步的几种方式 |
| 2 | + |
| 3 | +### 1.1信号量 |
| 4 | + |
| 5 | +用于进程间传递信号的一个整数值。在信号量上只有三种操作可以进行:初始化,P操作和V操作,这三种操作都是原子操作。 |
| 6 | + |
| 7 | +**P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。** |
| 8 | + |
| 9 | +基本原理是两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号。该信号即为信号量s。 |
| 10 | + |
| 11 | +为通过信号量s传送信号,进程可执行原语semSignal(s);为通过信号量s接收信号,进程可执行原语semWait(s);如果相应的信号仍然没有发送,则进程会被阻塞,直到发送完为止。 |
| 12 | + |
| 13 | +**可把信号量视为一个具有整数值的变量,在它之上定义三个操作:** |
| 14 | + |
| 15 | +- 一个信号量可以初始化为非负数 |
| 16 | +- semWait操作使信号量s减1.若值为负数,则执行semWait的进程被阻塞。否则进程继续执行。 |
| 17 | +- semSignal操作使信号量加1,若值大于或等于零,则被semWait操作阻塞的进程被解除阻塞。 |
| 18 | + |
| 19 | +### 1.2管程 |
| 20 | + |
| 21 | +管程是由一个或多个过程、一个初始化序列和局部数据组成的软件模块,**其主要特点如下:** |
| 22 | + |
| 23 | +- 局部数据变量只能被管程的过程访问,任何外部过程都不能访问。 |
| 24 | +- 一个进程通过调用管程的一个过程进入管程。 |
| 25 | +- 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。 |
| 26 | + |
| 27 | +管程通过使用条件变量提供对同步的支持,这些条件变量包含在管程中,并且只有在管程中才能被访问。有两个函数可以操作条件变量: |
| 28 | + |
| 29 | +- cwait(c):调用进程的执行在条件c上阻塞,管程现在可被另一个进程使用。 |
| 30 | +- csignal(c):恢复执行在cwait之后因为某些条件而阻塞的进程。如果有多个这样的进程,选择其中一个;如果没有这样的进程,什么以不做。 |
| 31 | + |
| 32 | +### 1.3消息传递 |
| 33 | + |
| 34 | +**消息传递的实际功能以一对原语的形式提供:** |
| 35 | + |
| 36 | +- send(destination,message) |
| 37 | +- receive(source,message) |
| 38 | + |
| 39 | +这是进程间进程消息传递所需要的最小操作集。 |
| 40 | + |
| 41 | +一个进程以消息的形式给另一个指定的目标进程发送消息; |
| 42 | + |
| 43 | +进程通过执行receive原语接收消息,receive原语中指明发送消息的源进程和消息。 |
| 44 | + |
| 45 | +## 2、进程互斥 |
| 46 | + |
| 47 | +由于进程具有独立性和异步性等并发特征,计算机的资源有限,导致了进程之间的资源竞争和共享,也导致了对进程执行过程的制约。 |
| 48 | + |
| 49 | +**1.临界区相关概念:** |
| 50 | +**临界资源**:也就是一次只允许一个进程操作使用的计算机资源,这里的资源可以是物理资源,也可以是逻辑上的变量等。 |
| 51 | +**临界区**:把不允许多个并发进程交叉执行的一段程序称为临界区(critical region)或临界部分(critical section)。 |
| 52 | +**当一个进程使用该临界资源时,其他需要访问该资源的进程必须阻塞,直到占用者释放该资源。** |
| 53 | + |
| 54 | +**2、间接制约** |
| 55 | +把这种由于共享某一公有资源而引起的在临界区内不允许并发进程交叉执行的现象,称为由共享公有资源而造成的对并发进程执行速度的间接制约。这里的“间接”二字主要是指各种并发进程的速度受公有资源的制约,而非进程之间的直接制约。 |
| 56 | + |
| 57 | +**3.进程互斥** |
| 58 | +在并发进程中,一个或多个进程要对公用资源进行访问时,必须确保该资源处于空闲状态,也就是说,在并发进程中,临界区只允许一个进程进入,而其他进程阻塞,等待该共享临界资源释放。 |
| 59 | + |
| 60 | +**4.并发进程之间必须满足以下特征:** |
| 61 | + |
| 62 | +- **平等竞争:**即各并发进程享有平等地、独立地竞争共有资源的权利,且在不采取任何措施的条件下,在临界区内任意指令结束时,其他并发进程可以进入临界区。 |
| 63 | +- **互斥使用**:当并发进程中的时候 多个进程同时申请进入临界区时,它只允许一个进程进入临界区。 |
| 64 | +- **不可独占:**当进程不在临界区后,它不能阻止其他进程进入临界区。 |
| 65 | +- **有限等待:**也就是在就绪队列中的进程等待资源时间必须是有限的。并发进程中的某个进程从申请进入临界区时开始,应在有限时间内得以进入临界区。 |
| 66 | + |
| 67 | +## 3、互斥的实现 |
| 68 | + |
| 69 | +### 2.1互斥的加锁实现 |
| 70 | + |
| 71 | +对互斥的临界区进行加锁处理,即当一个进程进入了 临界区之后,对此临界区进行加锁,直到该进程退出临界区为止。而其他并发进程在申请进入临界区之前,必须测试该临界区是否加锁,如果是,则阻塞等待! |
| 72 | +加锁实现是系统的原语:lock(key[S])和Unlock(key([S]))均保持原子操作。系统实现时锁定位key[S]总是设置在公有资源所对应的数据结构中的。 |
| 73 | + |
| 74 | +### 2.2互斥加锁实现的缺点 |
| 75 | + |
| 76 | +1.在进行锁测试和定位中将耗费CPU资源 |
| 77 | +2、进程加锁实现可能会对进程不公平,**例如:** |
| 78 | + |
| 79 | +```cpp |
| 80 | +进程A: |
| 81 | +lock(key[S]) |
| 82 | +<S> |
| 83 | +unlock(key[S]) |
| 84 | +Goto A |
| 85 | + |
| 86 | + |
| 87 | +进程B: |
| 88 | +lock(key[S]) |
| 89 | +<S> |
| 90 | +unlock(key[S]) |
| 91 | +Goto B |
| 92 | +``` |
| 93 | +
|
| 94 | +如上面所示,进程A和B之间的一个进程运行到Goto之后,会使得另一个进程无法得到处理机资源运行。而处于永久饥饿状态(starvation)。 |
| 95 | +
|
| 96 | +分析可以知道,一个进程能否进入临界区取决于进程自己调用lock过程去测试相应的锁定位。也就是说,每个进程能否进入临界区是依靠进程自己的测试判断。这样,没有获得执行机会的进程当然无法判断,从而出现不公平现象。 |
| 97 | +
|
| 98 | +那么是否有办法解决这个问题呢?当然,很明显,办法是有的,我们可以为临界区设置一个管理员,由这个管理员来管理相应临界区的公有资源,它代表可用资源的实体,这个管理员就是**信号量**。 |
| 99 | +
|
| 100 | +### 2.3信号量和P、V操作 |
| 101 | +
|
| 102 | +信号量和P、V原语是荷兰科学家E. W. Dijkstra提出来的。 |
| 103 | +**P原语:\**P是荷兰语Proberen(\***测试*)的首字母。为阻塞原因,负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作方法:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程会被阻塞; |
| 104 | +
|
| 105 | +**V原语**:V是荷兰语Verhogen(**增加**)的首字母。为**唤醒原语**,负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。 |
| 106 | +
|
| 107 | +**【信号量semaphore】** 在操作系统中,信号量sem是一个整数。 |
| 108 | +
|
| 109 | +- **sem >= 0时**,代表可供并发进程使用的资源实体数; |
| 110 | +- **sem < 0时**,表示正在等待使用临界区的进程数。 |
| 111 | +
|
| 112 | +显然,用于互斥的信号量sem的初值应该大于0,而建立一个信号量必须说明所建信号量代表的意义,赋初值,以及建立相应的数据结构,以便指向那些等待使用该临界区的进程。sem初值为1。 |
| 113 | +
|
| 114 | +**【P、V原语】** |
| 115 | +信号量的数值仅能由P、V原语操作改变。采用P、V原语,可以把类名为S的临界区描述为:When S do P(sem) 临界区 V(sem) od。 |
| 116 | +
|
| 117 | +- 一次**P原语操作使信号量sem减1** |
| 118 | +- 一次**V原语操作使信号量sem加1** |
| 119 | +
|
| 120 | +**P原语操作:** |
| 121 | +
|
| 122 | +sem减1; |
| 123 | +若sem减1后仍大于或等于0,则P原语返回,该进程继续执行; |
| 124 | +若sem减1后小于0,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。 |
| 125 | +
|
| 126 | +**V原语操作:** |
| 127 | +
|
| 128 | +sem加1; |
| 129 | +若相加结果大于0,V原语停止执行,该进程返回调用处,继续执行; |
| 130 | +若相加结果小于或等于0,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转进程调度。 |
| 131 | +
|
| 132 | +**这里给出一个使用加锁法的软件实现方法来实现P、V原语:** |
| 133 | +
|
| 134 | +```cpp |
| 135 | +P(sem): |
| 136 | + begin |
| 137 | + 封锁中断; |
| 138 | + lock(lockbit) |
| 139 | + val[sem]=val[sem]-1 |
| 140 | + if val[sem]<0 |
| 141 | + 保护当前进程CPU现场 |
| 142 | + 当前进程状态置为“等待” |
| 143 | + 将当前进程插入信号sem等待队列 |
| 144 | + 转进程调度 |
| 145 | + fi |
| 146 | + unlock(lockbit);开放中断 |
| 147 | + end |
| 148 | +V(sem): |
| 149 | + begin |
| 150 | + 封锁中断; |
| 151 | + lock(lockbit) |
| 152 | + val[sem]=val[sem]+1 |
| 153 | + if val[sem]<=0 |
| 154 | + local k |
| 155 | + 从sem等待队列中选取一个等待进程,将其指针置入k中 |
| 156 | + 将k插入就绪队列 |
| 157 | + 进程状态置位“就绪” |
| 158 | + fi |
| 159 | + unlock(lockbit);开放中断 |
| 160 | + end |
| 161 | +``` |
| 162 | + |
| 163 | +**2.3用P、V原语实现进程互斥** |
| 164 | +设信号量sem是用于互斥的信号量,且其初始值为1表示没有并发进程使用该临界区。显然,由前面论述可知,只要把临界区置于P(sem)和V(sem)之间,即可实现进程之间的互斥。 |
| 165 | + |
| 166 | +用信号量实现两个并发进程PA和PB互斥的描述如下: |
| 167 | +(1)设sem为互斥信号量,其取值范围为(1,0,-1)。其中sem=1表示进程PA和PB都未进入类名为S的临界区,sem=0表示进程PA或PB已进入类名为S的临界区,sem=-1表示进程PA和PB中,一个进程已进入临界区,而另一个进程等待进入该临界区。 |
| 168 | +(2)实现过程: |
| 169 | + |
| 170 | +```cpp |
| 171 | +Pa: |
| 172 | + P(sem) |
| 173 | + <S> |
| 174 | + V(sem) |
| 175 | + . |
| 176 | + . |
| 177 | + . |
| 178 | +Pb: |
| 179 | + P(sem) |
| 180 | + <S> |
| 181 | + V(sem) |
| 182 | + . |
| 183 | + . |
| 184 | + . |
| 185 | +``` |
| 186 | +
|
| 187 | +## 4,进程互斥的软件实现方法: |
| 188 | +
|
| 189 | +**1,单标志法** |
| 190 | +
|
| 191 | +1)在进入区只检查,不上锁 |
| 192 | +
|
| 193 | +2)在退出区把临界资源的使用权交给另一个进程 |
| 194 | +
|
| 195 | +3)主要问题:不遵循空闲让进的原则 |
| 196 | +
|
| 197 | + |
| 198 | +
|
| 199 | +**2,双标志先检查** |
| 200 | +
|
| 201 | +1)在进入区先检查后上锁,退出区解锁 |
| 202 | +
|
| 203 | +2)主要问题:不遵循原则等待原则 |
| 204 | +
|
| 205 | + |
| 206 | +
|
| 207 | +**3,双标志后检查** |
| 208 | +
|
| 209 | +1)在进入区先 |
| 210 | +
|
| 211 | +上锁后检查,退出区解锁 |
| 212 | +
|
| 213 | +2)主要问题:不遵循空闲让进,有限等待原则,可能导致饥饿 |
| 214 | +
|
| 215 | + |
| 216 | +
|
| 217 | +**4,Peterson算法** |
| 218 | +
|
| 219 | +1)在进入区主动争取——》主动谦让——》检查对方是否想进,己方是否谦让 |
| 220 | +
|
| 221 | +2)主要问题:不遵循让则等待原则,会发送忙等 |
| 222 | +
|
| 223 | + |
| 224 | +
|
| 225 | +## 5、进程同步 |
| 226 | +
|
| 227 | +**【进程间的直接制约】**:一组在异步环境下的并发进程,各自的执行结果互为对方的执行条件,从而限制各进程的执行速度的过程称为并发进程间的直接制约。这里的异步环境主要是指各并发进程的执行起始时间的随机性和执行速度的独立性。 |
| 228 | +
|
| 229 | +**【进程间的同步】**:把异步环境下的一组并发进程因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间相互发送的信号称为消息或事件。 |
| 230 | +**用消息实现进程同步:** |
| 231 | +
|
| 232 | +> 用 |
| 233 | +> wait(消息名) |
| 234 | +> 表示进程等待合作进程发来的消息。 |
| 235 | +> 用 |
| 236 | +> signal(消息名) |
| 237 | +> 表示向合作进程发送消息。 |
| 238 | +> 过程wait的功能是等待到消息名为true的进程继续执行,而signal的功能则是向合作进程发送合作进程所需要的消息名,并将其值置为true。 |
| 239 | +
|
| 240 | +**进程互斥和进程同步】**: |
| 241 | +进程同步不同于进程互斥,进程互斥时它们的执行顺序可以是任意的。一般来说,也可以把个进程之间发送的消息作为信号量看待。与进程互斥时不同的是,这里的信号量只与制约进程及被制约进程有关,而不是与整租并发进程有关。因此,称该信号量为私用信号量(private semaphore)。一个进程Pi的私用信号量semi是从制约进程发送来的进程Pi的执行条件所需要的信息。与私用信号量相对应,称互斥时使用的信号量为公用信号量。 |
| 242 | +
|
| 243 | +【**用P、V原语实现进程同步】:** |
| 244 | +首先为各并发进程设置私用信号量,然后为私用信号量赋初值,最后利用P、V原语和私用信号量规定各进程的执行顺序。 |
| 245 | +
|
| 246 | +------ |
| 247 | +
|
| 248 | +版权声明:本文为知乎博主「玩转Linux内核」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 |
| 249 | +原文链接:https://zhuanlan.zhihu.com/p/447720544 |
0 commit comments