2.3 进程同步
2.3.1 进程同步的基本概念
临界资源
一次仅允许一个进程使用的资源称为临界资源,如打印机等物理设备。对临界资源的访问必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。
对临界资源的访问可分为4个过程:
do{
entry section;//进入区
critical section;//临界区
exit section;//退出区
remainder section;//剩余区
}while(true)
同步(直接制约)
指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。
互斥(间接制约)
当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区时,另一种进程才允许访问此临界资源。
同步机制应遵循的准则:空闲让进、忙则等待、有限等待、让权等待。
2.3.2 信号量
信号量机制是用来解决互斥与同步问题的机制,它只能被两个标准的原语wait(S)
和signal(S)
访问,也称为“P操作”和“V操作”。
原语是指完成某种功能且不被分割、不被中断执行的操作序列,通常可由硬件实现。
整型信号量
wait(S){
while(S<=0)
S--;
}
signal(S){
S++;
}
记录型信号量
typedef struct{
int value;//资源数目
struct process *L;//进程链表
}semaphore;
//申请资源
void wait(semaphore S){
S.value--;
if(S.value<0){//资源分配完
add this process to S.L;
block(S.L);//自我阻塞
}
}
//释放资源
void signal(semaphore S){
S.value++;
if(S.value<=0){//仍有阻塞的进程
remove a process P from S.L;
wakeup(P);//唤醒S.L中第一个进程
}
}
利用信号量实现同步
当x执行完,y才可以执行。
semaphore S=0;//初始化信号量
P1(){
x; //x语句
V(S); //告诉进程P2,x已经完成
}
P2(){
P(S); //检查语句x是否运行完成
y; //检查无误,运行y语句
}
若P2先执行到P(S)时,S为0,执行P操作会把进程P2阻塞,并放入阻塞队列;当进程P1中的x执行完后,执行V操作,把P2从阻塞队列中放回就绪队列,当P2得到处理机时,得以继续运行。
利用信号量执行进程互斥
semaphore S=1;//初始化信号量,可以资源数为1
P1(){
P(S); //准备开始访问临界资源,加锁
进程P1的临界区;
V(S); //访问结束,解锁
}
P2(){
P(S); //准备开始访问临界资源,加锁
进程P2的临界区;
V(S); //访问结束,解锁
}
当没有进程在临界区时,任意一个进程要进入临界区,就要执行P操作:把S的值减为零,然后进入临界区;当有进程存在于临界区时,S的值为零,再有进程要进入临界区执行操作时将会被阻塞,直至在临界区中的进程退出,这样便实现了临界区的互斥。
2.3.3 管程
管程实质上是一个抽象类,这个抽象类有好几个成员变量,系统中任何设备都可通过这几个成员变量进行区分和描述;管程中,还有对这些成员变量进行操作的一组成员函数,例如,在对外设的操作中会有read,write这类函数,假如进程P0要使用一台打印机,于是管程这个抽象类就会利用初始值语句对自身的几个成员变量赋初值,特定的几个初值可让管城表示成一台打印机,进程P0进入管程后,通过调用过程中的成员函数对这台打印机进行操作,每次进入这个管程的只能是一个进程。
2.3.4 经典同步问题
生产者-消费者问题
问题描述:一组生产者进程和一组消费者进程共享一个初始为空,大小为n的缓冲区。只有缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不为空时,消费者才能从中取出消息,否则必须等待,由于缓冲区是临界资源,他只允许一个生产者放入消息,和一个消费者从中取出消息。
关系分析:生产者和消费者对缓冲区的访问是互斥关系。同时,生产者和消费者又是同步关系。
进程描述:
semaphore mutex=1; //临界区互斥信号量
semaphore empty=n; //空闲缓冲区
semaphore full=0; //已使用缓冲
prodecer(){//生产者进程
while(1){
produce an item in nextp;//生产数据
P(empty); //获取空缓存区单元
P(mutex); //互斥夹紧
add nextp to buffer; //将数据放入缓存区
V(mutex); //离开临界区,释放互斥信号
V(full); //已使用缓冲区数加一
}
}
prodecer(){//消费者进程
while(1){
P(full); //获取已使用缓存区单元
P(mutex); //互斥夹紧
remove an item from buffer;//从缓冲区取出数据
V(mutex); //离开临界区,释放互斥信号
V(full); //空缓冲区数加一
consume the item; //消费数据
}
}
生产者-消费者问题进阶
问题描述:桌子上有一个盘子,每次只能向其中放入一个水果,爸爸专门向盘子中放苹果,妈妈专门向盘子中放橘子,儿子穿等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可像盘子中放入一个水果,仅当盘子中有自己需要的水果时,儿子或女儿才可从盘子中取出。
关系分析:爸爸和妈妈是互斥关系,爸爸和女儿是同步关系,妈妈和儿子是同步关系,女儿和儿子没有同步和互斥关系。
进程描述:
semaphore plate=1, apple=0, orange=0;
dad(){
while(1){
prepare an apple;
P(plate); //互斥向盘中取放水果
put the apple on the plate;
V(apple); //允许取苹果
}
}
mom(){
while(1){
prepare an orange;
P(plate); //互斥向盘中取放水果
put the orangeon the plate;
V(orange); //允许取橘子
}
}
son(){
while(1){
P(orange); //互斥取橘子
put the orange on the plate;
V(plate); //允许向盘中取放水果
eat the orange;
}
}
mom(){
while(1){
P(apple); //互斥取苹果
put the apple on the plate;
V(plate); //允许向盘中取放水果
eat the apple;
}
}
读者-写者问题
问题描述:有读者和写者两组并发进程共享一个文件。要求:①允许多个读者可以同时对文件执行读操作②只允许一个写者往文件中写信息③任意写者在完成写操作之前,不允许其他读者和写者工作④写者执行写操作前,应让有的读者和写者全部退出。
关系分析:读者和写者互斥,写者和写者互斥,读者和读者不互斥。
进程描述:
int count=0; //用于记录当前读者数量
semaphore mutex=1; //用于保证更新count变量时的互斥
semaphore mutex=1; //用于保证读者和写着互斥访问文件
writer(){ //写者进程
while(1){
P(rw); //互斥访问共享文件
writing;
V(rw); //释放共享文件
}
}
reader(){ //读者进程
while(1){
P(mutex); //互斥访问count变量
P(rw); //阻止写进程写
count++;
V(mutex);
reading;
P(mutex); //互斥访问count变量
count--;
V(rw); //允许写进程写
V(mutex);
}
}
哲学家进餐问题
问题描述:一张圆桌边上,坐着五名哲学家。每两名哲学家之间的桌上摆一根筷子,两根筷子中间是一碗米饭。哲学家在思考时并不影响他人。只有当哲学家饥饿时才试图拿起左右两根筷子,若筷子已在他人手上,则需要等待。饥饿的哲学家。只有同时拿到了两根筷子才可以开始进餐,进餐完毕后放下筷子继续思考。
关系分析:相邻哲学家对中间筷子的访问是扶持关系。
进程描述:
semaphore chopsticks[5]={1,1,1,1,1};
semaphore mutex=1; //取筷子信号量
P(int i){ //i号哲学家的进程
do{
P(mutex); //在取筷子前获得互斥量
P(chopsticks[i]);//取左边筷子
P(chopsticks[(i+1)%5]);//取右边筷子
V(mutex); //释放取筷子信号量
V(chopsticks[i]);//放回左边筷子
V(chopsticks[(i+1)%5]);//放回右边筷子
think;
}while(1);
}
吸烟者问题
问题描述:假设一个系统有三个抽烟者进程和一个供应者进程,每个抽烟者不停的卷烟并抽烟,但要抽掉一支烟需要三种材料:烟草、纸、胶水。三个抽烟者中第一个有烟草,第二个有纸,第三个有胶水。供应者进程无限地提供三种材料,但每次仅将两种材料放到桌子上。剩下拥有那种材料的抽烟者卷一根烟抽调它,并给供应者一个信号,告诉已完成,此时供应者会将另外两种材料放到桌上,如此重复,让三个抽烟者轮流地抽烟。
关系分析:供应者与三个抽烟者分别是同步关系,三个抽烟者对抽烟这个动作互斥。
进程描述:
semaphore offer1=0;//烟草和纸组合的资源
semaphore offer2=0;//烟草和胶水组合的资源
semaphore offer3=0;//胶水和纸组合的资源
semaphore finish=0;//抽烟是否完成
Process P1(){ //供应者
while(1){
int random;
random=random%3;
if(random==0)
V(offer1);//提供烟草和纸
else if(random==1)
V(offer2);//提供烟草和胶水
else
V(offer1);//提供胶水和纸
P(finish);
}
}
Process P2(){ //拥有烟草者
while(1){
P(offer3);
卷烟,抽掉;
V(finish);
}
}
Process P3(){ //拥有纸者
while(1){
P(offer2);
卷烟,抽掉;
V(finish);
}
}
Process P4(){ //拥有胶水者
while(1){
P(offer1);
卷烟,抽掉;
V(finish);
}
}
附:java实现的生产者-消费者模型
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
/**
* 生产者消费者问题:使用Object.wait() / notify()方法实现。
* Object.wait():当缓冲区空/满时,消费者/生产者线程停止执行。
* Object.notify():当消费者/生产者执行完动作,向其他线程发出可执行的通知。
*/
public class ProducersAndConsumers {
private static final int CAPACITY = 5;//缓冲区容量
public static void main(String args[]){
Queue<Integer> queue = new LinkedList<Integer>();
//创建一个生产者,一个消费者(也可以是多个)
Thread producer = new Producer("Producer", queue, CAPACITY);
Thread consumer = new Consumer("Consumer", queue, CAPACITY);
producer.start();
consumer.start();
}
// 生产者
public static class Producer extends Thread{
private Queue<Integer> queue;
String name;
int maxSize;
int i = 0;
public Producer(String name, Queue<Integer> queue, int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run(){
while(true){
synchronized(queue){
while(queue.size() == maxSize){
try {
System.out .println("缓冲区已满, 生产者" + name + "需要等待消费者消费。");
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
System.out.println(name + " 生产数据:" + i);
queue.offer(i++);
queue.notifyAll();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 消费者
public static class Consumer extends Thread{
private Queue<Integer> queue;
String name;
int maxSize;
public Consumer(String name, Queue<Integer> queue, int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run(){
while(true){
synchronized(queue){
while(queue.isEmpty()){
try {
System.out.println("缓冲区空,消费者" + name + "要等待生产者生产。");
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
int x = queue.poll();
System.out.println(name + " 把数据 " + x + " 消费了。");
queue.notifyAll();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}