文章目录
-
- 进程同步
-
- 1.进程同步的基本概念
-
-
-
- 两种制约关系
- 临界资源和临界区
- 同步机制应该遵循的规则
-
-
- 2.信号量机制
-
-
-
- 整型信号量
- 记录型信号量
-
-
- 3.信号量应用
-
-
-
- 利用信号量实现前趋关系
- 利用信号量实现互斥
-
-
- 4.经典进程的同步问题
-
-
-
- 生产者—消费者问题(个人理解)
- 读者—写者问题(个人理解)
- 生产者—消费者问题变形(个人理解)
-
-
进程同步
1.进程同步的基本概念
进程同步的主要任务是对多个相关进程在执行次序上进行协调,
使并发执行的诸进程之间能够按照一定的规则(或时序)共享系
统资源,并能很好的相互合作,从而使程序的执行具有可再现性。
两种制约关系
在多道程序环境下,对于处于一个系统中的多个进程,由于他们共享资源,或为完成某一任务而相互合作,他们之间可能存在着以下两种形式的制约关系:
1)间接相互制约:多个程序在并发执行时,由于共享系统资源,如CPU,I/O设备等,致使这些并发执行的程序之间形成相互制约的关系。为了保证这些进程的能有序执行,对于系统中的这类资源,必须由系统统一分配,即用户在使用之前,应先提出申请,而不允许用户进程直接使用。这种制约关系源于对该类资源的共享。
2)直接相互制约关系:为了完成某任务而建立的两个或者多个进程。这些进程将为完成同一项任务而相互合作。例如生产者——消费者问题就存在着相互制约关系进程间的直接制约关系源于他们之间的相互合作。
在多道程序环境下,由于存在着上述两类相互制约关系,进程运行过程中是否能获得处理机运行以及以怎样速度运行,并不是进程自身能控制的,此即进程的异步性。
临界资源和临界区
在计算机中有许多资源一次只能允许一个进程使用,如果多个进程同时使用则可能造成系统的混乱,这些资源被称作临界资源(比如打印机)。多个进程同时使用临界资源会使其结果具有不可再现性。
在每个进程中,访问临界资源的那段代码称作临界区。为了使进程能有效共享临界资源,并使程序的执行具有可再现性,系统必须保证进程互斥的使用临界资源,即保证他们互斥的进入临界区。因此,必须在临界区前面加一段代码,用来检查对应的临界资源是否正被其他进程访问,这段代码称为进入区;相应的在临界区后边也要加一段代码称为退出区,用于将临界区从正在被访问的标志恢复为未被访问的标志。
同步机制应该遵循的规则
用来实现互斥的共享机制必须遵守以下四个规则:
1)空闲让进:临界区空闲时,应允许一个请求进入临界区的进程立即进入临界区,以便有效的利用资源。
2)忙则等待:当临界资源正在访问时,其他要求进入临界区的进程必须等待,以保证对临界资源的互斥访问。
3)有限等待:任何要求访问临界资源的进程应能在有限时间内进入临界区。
4)让权等待:不能进入临界区的进程应立即释放CPU资源,以免“忙等”。
2.信号量机制
介绍:信号量机制是荷兰学者Dijkstra在1965年提出的一种同步机制。
本篇文章注重介绍整型信号量和记录型信号量。
整型信号量
最初由Dijkstra把整型信号量定义为一个用于表示资源数目的整型量S,除了初始化外,它仅能通过两个标准的原子操作wait(P)和signal(V)来访问。算法如下:
wait(S){
while(S <= 0);
*表示资源正被占用,循环等待(不遵循“让权等待”的准则
而是使之处于忙等状态)*
S--;
}
signal(S){
S++;
}
记录型信号量
记录型信号量除了需要一个用于代表资源数目的整型变量value外,还增加了一个进程链表指针list,用于链接所有等待该资源的进程。其算法如下:
typedef struct{
int value;
struct process_control_block *list;
} semaphore;
wait和signal操作可描述为:
wait(semaphore *S){
S->value--;
*wait申请资源,资源数减1*
if(S->value < 0)
*S->value自减之后<0则表示系统中已无资源分配*
block(S->list);
*block自我阻塞,放弃CPU,并插入到S->list中*
}
signal(semaphore *S){
S->value++;
*释放资源,value加1*
if(S->value <= 0)
*value加1之后value仍然<=0,表示仍然有等待该资源
的进程被阻塞,所以用wakeup将等待链表中的第一
个进程唤醒*
wakeup(S->value);
}
3.信号量应用
利用信号量实现前趋关系
前趋关系在上一篇文章已将这里就不多说,直接上图写关系
代码框架如下:
P1(){signal(a);}
p2(){wait(a); signal(b); signal(c);}
P3(){wait(b); wait(d); signal(e);}
P4(){wait(c); signal(d); signal(f);}
P5(){wait(f); wait(e);}
利用信号量实现互斥
为了使多个进程能互斥地访问某个临界资源,只需为该资源设置一互斥信号量mutex ,并将其信号量设置为1,然后将访问该临界区的资源放wait(mutex)和signal(mutex)之间。下面算法描述了如何利用信号量实现进程P1和P2的互斥:
semaphore mutex = 1;
p1(){
while(1){
wait(mutex);
临界区
signal(mutex)
剩余区
}
}
p2(){
while(1){
wait(mutex);
临界区
signal(mutex)
剩余区
}
}
在利用信号量机制实现进程互斥时需要注意,wait(mutex)和signal(mutex)必须成对出现,(成对出现不一定在一个进程中成对出现)缺少wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问;而缺少signal(mutex)j将会使临界资源永远不被释放,从而使因等待该资源的进程不能被唤醒。
4.经典进程的同步问题
生产者—消费者问题(个人理解)
(一个缓冲区的就不说了)如果一个生产者一个消费者n个缓冲区:
设置三个信号量:
互斥信号量mutex,用于实现对缓冲池的互斥访问,初始值为1;
资源信号量empty,用来表示空闲缓冲区的数量,初始值n;
资源信号量full,用来表示满缓冲区的数量,初始值为0;
empty和full用来同步生产者和消费者进程。
具体算法描述如下:
int in = 0,out = 0;
item buffer[n]
semaphore mutex = 1, empty = n, full = 0;
//生产者
void proceducer(){
do{
produce an item nextp;
...
wait(empty);
//判断是否有空闲缓冲池,并申请资源,如果有,则empty(空缓冲区)减一
wait(mutex)
//申请缓冲区资源,并进入缓冲区(准备进行操作)
buffer[in] = nextp;
//将产品放入缓冲区
in = (in + 1) % n;
signal(mutex);
//释放缓冲区资源
signal(full);
//满缓冲区数量加1,释放资源
}while(TRUE);
}
//消费者
void consumer(){
do{
wait(full);
//判断是否有满缓冲区,并申请资源,如果有,则full空缓冲区)减一
wait(mutex);
//申请缓冲区资源,并进入缓冲区(准备进行操作)
nextc = buffer[out];
//拿出产品
out = (out + 1) % n;
signal(mutex)
//释放缓冲区资源
signal(empty);
//空缓冲区数量加1,释放资源
consume the item in nextc;
...
}while(TRUE);
}
注:n个缓冲池用循环队列
当(in + 1)% n = out,表示进来一圈了,也就是缓冲池满了;
当in = out表示缓冲池空;
读者—写者问题(个人理解)
题述:一个数据对象,如笔记或者记录,能被多个进程共享,可把那些只要求读数据对象的进程称为"读者",其他进程则称为写者。显然读者可同时读一个共享对象,但不允许读者和写着同时访问共享对象,也不允许多个写着同时访问共享对象。
如考虑读者优先,即除非有读者在读,或者读者就不用等待,该问题算法可描述为:
互斥信号量mutex:实现Reader和Writer进程间在读或者写时的互斥。
互斥信号量rmutex:实现对readcount互斥访问。
整型变量readcount:记录读者数量。
semaphore rmutex = 1, mutex = 1;
int readcount = 0;
//读者
void reader(){
do{
//进读者
wait(rmutex);
//判断readcount资源是否被占用,若无,则申请资源
if(readcount == 0) wait(mutex);
//如果没有读者,则先申请读写缓冲区的资源
readcount ++;
//读者数量加1
signal(rmutex)
//释放对readcount资源的占用
...
perform read operation;
...
//出读者
wait(rmutex);
//判断readcount资源是否被占用,若无,则申请资源
readcount --;
//读者数量减1
if(readcount == 0) signal(mutex);
//如果读者出完,则把对读写缓冲区的资源释放
signal(rmutex);
//释放对readcount资源的占用
}while(TRUE);
}
//写者
void writer(){
do{
wait(mutex);
//写者只能有一个,不需要在申请对readcount的占用
//所以直接申请读写缓冲区资源
perform write operation;
signal(mutex);
//释放对读写缓冲区的占用
}while(TRUE);
}
生产者—消费者问题变形(个人理解)
问题描述:桌子上有能盛的下5个水果的空盘子,爸爸不停的向盘子中放苹果或者橘子,儿子不停的从盘子中取走橘子,女儿不停从盘中取出苹果,规定三人不能同时从盘子中取放水果。试用信号量实现爸爸,儿子和女儿这三个循环进程之间的同步。
本题是生产者—消费者问题的变形,为一个生产者,多个缓冲区,多个消费者。
需要设置两个不同的full变量orange和apple初始值为0。
三人不能同时取放水果,设置互斥信号量mutex以实现三人对临界资源的互斥访问。
还需设置一个empty记录盘子中的空位置(五个位置)
该问题算法可描述为:
semaphore empty = 5, orange = 0, apple = 0, mutex = 1;
Dad(){
do{
wait(empty);
//判断是否还有空位置,若有,申请相应资源
wait(mutex);
//判断是否临界资源被其他(儿子,女儿)进程占用
//若没有,这申请资源,进入缓冲区,被占用则等待
放水果操作
signal(mutex);
//释放对临界资源的占用
if(orange) signal(orange)
else signal(apple)
//如果放入的是橘子,则橘子数量加1,释放orange
//否则对apple进行操作
}while(TRUE)
}
Daughter(){
do{
wait(apple)
//判断盘子里是否有苹果,如果有,则申请相应资源
wait(mutex)
//申请进入缓冲区,mutex减1,资源被Daughter占用,准备对苹果进行操作
拿走苹果
signal(mutex)
//拿走苹果之后,把占用的临界资源释放
signal(empty)
//苹果被拿走,空位置(缓冲区)加1
}while(TRUE)
}
Son(){
do{
wait(orange)
//判断盘子里是否有橘子,如果有,则申请相应资源,orange减1
wait(mutex)
//申请进入缓冲区,mutex减1,资源被Son占用,准备对橘子进行操作
拿走苹果
signal(mutex)
//拿走橘子之后,把占用的临界资源释放
signal(empty)
//橘子被拿走,空位置(空缓冲区)加1
}while(TRUE)
}
注:以上仅为个人理解,如有错误,还望提出,占占积极改正!