Python进阶学习笔记之——经典的进程同步问题

经典的进程同步问题

1、信号量

信号量机制是一种功能较强大的机制,可用来解决互斥和同步问题,它只能被两个标准的原语 wait(S) 和 signal(S) 访问,也可记为 “P 操作” 和 ”V 操作“

1.1、整型信号量

整型信号量被定义为一个用来表示资源数目的整型量 S,wait 和 signal 操作可描述为:

wait(S){
    
    
	while(S<=0);
	S = S-1;
}
signal(S){
    
    
	S = S+1;
}

1.2、记录型信号量

typedef struct{
    
    
	int value;
	struct process *L;
}semaphore;

wait 和 signal 操作可描述为:

void wait(semaphore S){
    
    		//相当于申请资源
	S.value--;
	if(S.value < 0){
    
    
		插入等待队列;
		block(S.L);
	}
}
void signal(semaphore S){
    
    		//相当于释放资源
	S.value++;
	if(S.value <= 0){
    
    
		从队列中唤醒一个进程;
		wakeup(P);
	}
}

2、经典的同步问题

2.1、生产者-消费者问题

2.1.1、生产者-消费者问题

见《Python进阶学习笔记之——多线程编程》。

2.1.2、多生产者-多消费者问题

问题描述:

桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专门向盘子中放苹果,妈妈专门向盘子中放橘子,儿子专门等吃盘子中的橘子,女儿专门等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才能向盘子中放一个水果;仅当盘子中有自己吃的水果时,女儿或儿子可以从盘子中取出。

伪代码:

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 orange on the plate;
	V(porange);				//允许取橘子
	}
}

daughter(){
    
    				//女儿进程
    while(1){
    
    
        P(apple);			//互斥从盘中取苹果
        take an apple from the plate;
        V(plate);			//允许向盘中放水果
        eat the apple
    }
}

son(){
    
    			//儿子进程
    while(1){
    
    
        P(orange);			//互斥从盘中取橘子
        take an orange from the plate;
        V(plate);			//允许向盘中放水果
        eat the orange;
    }
}

Python 语言实现:

from threading import Thread,Semaphore
from queue import Queue
from random import randint
from time import sleep

class MyThread(Thread):					#创建进程类
    def __init__(self,func,args,name=''):
        Thread.__init__(self)
        self.name = name
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)

plate = Semaphore(1)		#盘子信号量
orange = Semaphore(0)		#橘子信号量
apple = Semaphore(0)		#苹果信号量
q = Queue(1)			#共享存储

def dad(queue):
    while(True):
        plate.acquire()			#互斥向盘子中放水果
        queue.put('apple')
        print("爸爸准备一个苹果...")
        apple.release()			#释放盘子,允许取苹果
        sleep(randint(2,5))

def mom(queue):
    while (True):
        plate.acquire()			#互斥向盘子中放水果
        queue.put('orange')
        print('妈妈准备了一个橘子...')
        orange.release()		#允许取橘子
        sleep(randint(2,5))

def son(queue):
    while(True):
        orange.acquire()		#取橘子
        queue.get()
        print('儿子拿走了一个橘子')
        plate.release()			#盘子为空
        sleep(randint(1, 3))

def daughter(queue):
    while(True):
        apple.acquire()			#取苹果
        queue.get()
        print('女儿拿走了苹果')
        plate.release()			#盘子为空
        sleep(randint(1, 3))

funs = [dad,mom,son,daughter]

threads = []

for fun in funs:
    t = MyThread(fun,(q,),fun.__name__)
    threads.append(t)

for i in range(len(funs)):
    threads[i].start()
爸爸准备一个苹果...
女儿拿走了苹果
妈妈准备了一个橘子...
儿子拿走了一个橘子
妈妈准备了一个橘子...
儿子拿走了一个橘子
爸爸准备一个苹果...
女儿拿走了苹果
妈妈准备了一个橘子...
儿子拿走了一个橘子
爸爸准备一个苹果...
女儿拿走了苹果
...

2.2、读者-写者问题

问题描述:

有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会发生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:(1)允许多个读者可以同时对文件执行读操作;(2)只允许一个写者往文件中信息;(3)任一写者在完成写操作之前不允许其他读者或者写者工作;(4)写者执行写操作前,应让所有读者和写者全部退出。

伪代码:

int count = 0;			//用于记录当前的读者数量
semaphore mutex = 1;	//用于保护更新 count 变量时的互斥
semaphore rw = 1;		//用于保证读者和写者互斥地访问文件
semaphore w = 1;		//用于实现 “写优先”

writer(){
    
    
	while(1){
    
    
	P(w);		//在无写进程请求时进入
    P(rw);		//互斥地访问共享文件
    writing;
    V(rw);		//释放共享文件
    V(w);		//恢复对共享文件的访问
	}
}

reader(){
    
    
	while(1){
    
    
	P(w);		//在无写进程请求时进入
	P(mutex);		//互斥地访问 count 变量
	if(count == 0){
    
    		//当第一个读进程共享文件时
		P(rw);		//阻止写进程写
	}
	count++;		//读者计数器加 1
	V(mutex);		//释放互斥变量
	V(w);			//恢复对共享文件的访问
	reading;
	P(mutex);		//互斥访问 count 变量
	count--;
	if(count == 0){
    
    		//当最后一个进程读完共享文件
		V(rw);			//允许写进程写
	}
	V(mutex);		//释放互斥变量 count
	}
}

Python 语言实现:

import MyThread				#把实现进程类作为模块导入
from threading import Semaphore, Lock
from time import sleep
from random import randint

count = 0
lock = Lock()			#互斥访问全局变量直接使用锁
rw = Semaphore(1)
w = Semaphore(1)


def writer():
    while (True):
        w.acquire()
        rw.acquire()
        print('写入文件')
        rw.release()
        w.release()
        sleep(randint(2, 4))


def reader():
    global count
    while (True):
        w.acquire()
        lock.acquire()
        if count == 0:
            rw.acquire()
        count += 1
        lock.release()
        w.release()
        print('目前有:', count, '个读者正在读文件')
        sleep(randint(3, 5))
        lock.acquire()
        count -= 1
        print('有一位读者退出,目前还有:', count, '个读者')
        if count == 0:
            rw.release()
        lock.release()
        sleep(randint(1, 3))

for i in range(randint(2,5)):
    MyThread.MyThread(reader,(),reader.__name__).start()

for i in range(randint(1,3)):
    MyThread.MyThread(writer,(),writer.__name__).start()
目前有: 1 个读者正在读文件
目前有: 2 个读者正在读文件
有一位读者退出,目前还有: 1 个读者
有一位读者退出,目前还有: 0 个读者
写入文件
目前有: 1 个读者正在读文件
目前有: 2 个读者正在读文件
有一位读者退出,目前还有: 1 个读者
有一位读者退出,目前还有: 0 个读者
写入文件
...

2.3、哲学家进餐问题

问题描述:

一张圆桌上坐着 5 名哲学家,每两名哲学家之间的桌子上摆着一根筷子,两根筷子之间是一碗米饭。哲学家倾注毕生精力思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根的拿起)。若筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿到了两根筷子才可以进餐,进餐完毕后,放下筷子继续思考。

伪代码:

semaphore chopstick[5] = {
    
    1,1,1,1,1};		//初始化信号量
semaphore mutex = 1;				//取筷子的信号量
Pi(){
    
    
	do{
    
    
		P(mutex);		//在取筷子前获得信号量
		P(chopstick[i]);	//取左边筷子
		P(chopstick[(i+1)%5);	//取右边筷子
        V(mutex);		//释放取筷子的信号量
        eat;
        V(chopstick[i]);		//放回左边筷子
        V(chopstick[(i+1)%5);	//放回右边筷子
        think;
	}
	while(1);
}

Python 语言实现:

import MyThread
from threading import Lock,Semaphore
from time import sleep
from random import randint

chopstick1 = Semaphore(1)
chopstick2 = Semaphore(1)
chopstick3 = Semaphore(1)
chopstick4 = Semaphore(1)
chopstick5 = Semaphore(1)
lock = Lock()

def P1():
    while(True):
        lock.acquire()
        chopstick1.acquire()
        chopstick2.acquire()
        lock.release()
        print('第1号哲学家正在进餐')
        sleep(randint(1, 3))
        chopstick1.release()
        chopstick2.release()
        print('第1号哲学家吃完了饭,继续思考...')

def P2():
    while(True):
        lock.acquire()
        chopstick2.acquire()
        chopstick3.acquire()
        lock.release()
        print('第2号哲学家正在进餐')
        sleep(randint(1, 3))
        chopstick2.release()
        chopstick3.release()
        print('第2号哲学家吃完了饭,继续思考...')

def P3():
    while(True):
        lock.acquire()
        chopstick3.acquire()
        chopstick4.acquire()
        lock.release()
        print('第3号哲学家正在进餐')
        sleep(randint(1, 3))
        chopstick3.release()
        chopstick4.release()
        print('第3号哲学家吃完了饭,继续思考...')

def P4():
    while(True):
        lock.acquire()
        chopstick4.acquire()
        chopstick5.acquire()
        lock.release()
        print('第4号哲学家正在进餐')
        sleep(randint(1, 3))
        chopstick4.release()
        chopstick5.release()
        print('第4号哲学家吃完了饭,继续思考...')

def P5():
    while(True):
        lock.acquire()
        chopstick5.acquire()
        chopstick1.acquire()
        lock.release()
        print('第5号哲学家正在进餐')
        sleep(randint(1, 3))
        chopstick5.release()
        chopstick1.release()
        print('第5号哲学家吃完了饭,继续思考...')

MyThread.MyThread(P1,(),P1.__name__).start()
MyThread.MyThread(P3,(),P3.__name__).start()
MyThread.MyThread(P2,(),P2.__name__).start()
MyThread.MyThread(P5,(),P5.__name__).start()
MyThread.MyThread(P4,(),P4.__name__).start()

不知道为什么列表定义 chopstick = [Semaphore(1)]*5,无法运行。待解决。

1号哲学家正在进餐
第3号哲学家正在进餐				#1 号进餐时 3 号可以进餐,并不冲突3号哲学家吃完了饭,继续思考...1号哲学家吃完了饭,继续思考...2号哲学家正在进餐
第5号哲学家正在进餐
第2号哲学家吃完了饭,继续思考...5号哲学家吃完了饭,继续思考...4号哲学家正在进餐
第4号哲学家吃完了饭,继续思考...3号哲学家正在进餐
第1号哲学家正在进餐
第1号哲学家吃完了饭,继续思考...3号哲学家吃完了饭,继续思考...2号哲学家正在进餐
第5号哲学家正在进餐
...

2.4、吸烟者问题

问题描述:

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应商无限地提供三种材料,供应商每次将两种材料放到桌子上,拥有剩余那种材料的抽烟者卷起一根烟并抽掉它,并给供应者一个信号量告诉以完成,此时供应这会将另外两种材料放到桌子上,如此重复。

伪代码:

int = random;		//存储随机数
semaphore offer1 = 0;	//定义信号量对应烟草和纸的组合
semaphore offer2 = 0;	//定义信号量对应烟草和胶水的组合
semaphore offer3 = 0;	//定义信号量对应纸和胶水的组合
semaphore finish = 0;	//定义信号量表示抽烟完成

process P1(){
	while(1){
		random = 随机数;
		random = random % 3;
		if(random == 0)
			V(offer1);		//提供烟草和纸
		else if(random == 1)
			V(offer2);		//提供烟草和胶水
		else
			V(offer3);		//提供纸和烟草
		任意两种材料放在桌子上;
		P(finish);
	}
}

process P2(){		//拥有烟草者
	while(1){
		P(offer3);
		拿纸和胶水,卷烟,抽掉;
		V(finish);
	}
}

process P2(){		//拥有纸者
	while(1){
		P(offer2);
		拿烟草和胶水,卷烟,抽掉;
		V(finish);
	}
}

process P2(){		//拥有胶水者
	while(1){
		P(offer1);
		拿烟草和纸,卷烟,抽掉;
		V(finish);
	}
}

Python 语言实现:

import MyThread
from threading import Semaphore
from time import sleep
from random import randint

offer1 = Semaphore(0)
offer2 = Semaphore(0)
offer3 = Semaphore(0)
finish = Semaphore(0)

def supplier():
    while (True):
        random = randint(1,3)
        if random == 1:
            print('【供应商提供了:烟草和纸】')
            offer1.release()
        elif random == 2:
            print('【供应商提供了:烟草和胶水】')
            offer2.release()
        else:
            print('【供应商提供了:纸和胶水】')
            offer3.release()
        finish.acquire()

def smoking1():
    while(True):
        offer3.acquire()
        print('*****我是1号吸烟者,我拥有烟草***')
        print('拿到了纸和胶水,开始吸烟')
        sleep(randint(2, 4))
        finish.release()

def smoking2():
    while(True):
        offer2.acquire()
        print('*****我是2号吸烟者,我拥有纸***')
        print('拿到了烟草和胶水,开始吸烟')
        sleep(randint(2, 4))
        finish.release()

def smoking3():
    while(True):
        offer1.acquire()
        print('*****我是3号吸烟者,我拥有胶水***')
        print('拿到了烟草和纸,开始吸烟')
        sleep(randint(2, 4))
        finish.release()

func = [supplier,smoking1,smoking2,smoking3]

for fun in func:
    MyThread.MyThread(fun,(),fun.__name__).start()
【供应商提供了:烟草和纸】
*****我是3号吸烟者,我拥有胶水***
拿到了烟草和纸,开始吸烟
【供应商提供了:烟草和胶水】
*****我是2号吸烟者,我拥有纸***
拿到了烟草和胶水,开始吸烟
【供应商提供了:烟草和纸】
*****我是3号吸烟者,我拥有胶水***
拿到了烟草和纸,开始吸烟
【供应商提供了:纸和胶水】
*****我是1号吸烟者,我拥有烟草***
拿到了纸和胶水,开始吸烟
...

猜你喜欢

转载自blog.csdn.net/qq_36879493/article/details/108186083