进程同步控制--锁、信号量、事件
********进程同步控制******** ****锁--multiprocessing.Lock**** 通过学习,我么实现了程序的异步,让多个任务可以同时在几个进程中处理,他们之间的运行没有顺序,一旦 开启不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题 当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。 **多进程抢占资源 import os import time import random from multiprocessing import Process def work(n): print('%s: %s is running' % (n, os.getpid())) time.sleep(random.random()) print('%s:%s is done' % (n, os.getpid())) if __name__ == '__main__': for i in range(3): p = Process(target=work, args=(i,)) p.start() **使用锁维护执行顺序 由并发变成了串行,牺牲了运行效率,但避免了竞争 import time import random import os from multiprocessing import Process,Lock def work(n, l): l.acquire() print('%s: %s is running' % (n, os.getpid())) time.sleep(random.random()) print('%s:%s is done' % (n, os.getpid())) l.release() if __name__ == '__main__': l = Lock() for i in range(3): p = Process(target=work, args=(i,l)) p.start() 上面这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。 接下来,我们以模拟抢票为例,来看看数据安全的重要性。 from multiprocessing import Lock, Process import time def show(i): # 产看余票信息 with open('余票') as f: temp = f.read() print('第%s个人查到还有余票%s' % (i, temp)) def buy(i, l): # 买票 l.acquire() with open('余票') as f: temp = int(f.read()) time.sleep(0.1) if temp > 0: temp -= 1 print('\033[31m 第%s个人买到了票\033[0m' % i) else: print('\033[32m第%s个人没有买到票\033[0m' % i) time.sleep(0.1) with open('余票', 'w') as f: f.write(str(temp)) l.release() if __name__ == '__main__': l = Lock() for i in range(10): # 多用户查看票数 p = Process(target=show, args=(i + 1,)) p.start() for i in range(10): # 模拟并发多个用户抢票 p = Process(target=buy, args=(i + 1, l)) p.start() #加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改, #即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 虽然可以用文件共享数据实现进程间通信,但问题是: 1.效率低(共享数据基于文件,而文件是硬盘上的数据) 2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾: 1、效率高(多个进程共享一块内存的数据) 2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题, 而且在进程数目增多时,往往可以获得更好的可获展性。 ****信号量**** 介绍: 互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据 。 假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。 实现: 信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时, acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问 像服务器这样的有限资源。 信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念 from multiprocessing import Process, Semaphore import time, random def work(sem, user): sem.acquire() print('%s 占到一个房间' % user) time.sleep(random.random()) # 模拟在房间中待的时间 sem.release() if __name__ == '__main__': sem = Semaphore(4) p_ls = [] for i in range(10): p = Process(target=work, args=(sem, 'user %s' % i,)) p.start() p_ls.append(p) [i.join() for i in p_ls] print('>>>>>>>>>>>>>>>>>>>') ****事件**** python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。 事件处理的机制:is_set() 方法的值,默认为False 那么当程序执行 event.wait 方法时就会阻塞, 如果is_set()值为True,那么event.wait 方法执行时便不再阻塞。 clear()方法:将is_set()的值设置为False set()方法:将is_set()的值设置为True 红绿灯实例: from multiprocessing import Process, Event import time import random # 亮灯和车行驶是异步的 # 亮灯的进程先开启 # is_set()刚开始默认为False # 先开始l绿灯亮,这个时候car的进程开启,is_set()为True,可以过车 def light(e): while 1: if e.is_set(): e.clear() print('\033[31m红灯亮了\033[0m') else: e.set() print('\033[32m绿灯亮了\033[0m') time.sleep(2) def cars(i, e): if not e.is_set(): print('第%s辆车在等待' % i) e.wait() print('第%s辆车过去了' % i) if __name__ == '__main__': e = Event() tr = Process(target=light, args=(e,)) # 创建一个进程控制红绿灯 tr.start() for i in range(50): # 创建50个进程代表50辆车 car = Process(target=cars, args=(i + 1, e)) car.start() time.sleep(random.random())