进程同步控制 —— 锁\信号量\事件
锁 —— multiprocessing.Lock
通过刚刚的学习,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序(或者说由操作系统调度决定他们的顺序),一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。
加锁的形式确保了程序的顺序执行,但是执行又变成了串行,降低了效率,但是不得不说,它确保了数据的安全性。
l = Lock()
一把钥匙配一把锁
拿钥匙,锁门 l.acquire ( acquire: 获取 )
还钥匙,开门 l.release (release: 释放)
from multiprocessing import Lock l = Lock() l.acquire()# 拿走钥匙,锁门,不让其他人进屋 l.release()# 释放锁。 还钥匙,开门,允许其他人进屋
# -*- coding:utf-8 -*- # Author : Niuli # Data : 2018/8/21 下午3:59 from multiprocessing import Process, Lock, Value import time def get_money(num, l): l.acquire() # 拿走钥匙,锁门 for i in range(100): # 100块,每次取1块 num.value -= 1 print('取:', num.value) time.sleep(0.01) l.release() # 还钥匙,开门,下一位可以进入 def put_money(num, l): l.acquire() # 拿走钥匙,关上门 for i in range(101): # 100块,每次存1块 num.value += 1 print('存:', num.value) time.sleep(0.01) l.release() # 还钥匙.开门,下一位可以进入 if __name__ == '__main__': num = Value('i', 100) # 类似 struck 需要一个类型,一个数据 l = Lock() # 实例化Lock的对象l (需要先制造一把锁) p_get = Process(target=get_money, args=(num, l)) # 创建取钱的进程,传两个参数:钱数,锁 p_put = Process(target=put_money, args=(num, l)) # 创建存钱的进程,传两个参数:钱数,锁 p_get.start() # 开启取钱的进程 p_put.start() # 开启存钱的进程 # 没有join 会异步执行,主程序会先执行完,打印最终的钱数 会是最开始的钱数 # 没有join会先执行完主程序 # 有join,主程序会等着子程序执行完后再接着执行 p_get.join() # 异步转同步,让子进程先执行,主程序等着 p_put.join() # 异步转同步,让子进程先执行,主程序等着 print('这里是最终的钱数:', num.value)
取: 99 取: 98 取: 97 取: 96 取: 95 取: 94 取: 93 取: 92 取: 91 取: 90 取: 89 取: 88 取: 87 取: 86 取: 85 取: 84 取: 83 取: 82 取: 81 取: 80 取: 79 取: 78 取: 77 取: 76 取: 75 取: 74 取: 73 取: 72 取: 71 取: 70 取: 69 取: 68 取: 67 取: 66 取: 65 取: 64 取: 63 取: 62 取: 61 取: 60 取: 59 取: 58 取: 57 取: 56 取: 55 取: 54 取: 53 取: 52 取: 51 取: 50 取: 49 取: 48 取: 47 取: 46 取: 45 取: 44 取: 43 取: 42 取: 41 取: 40 取: 39 取: 38 取: 37 取: 36 取: 35 取: 34 取: 33 取: 32 取: 31 取: 30 取: 29 取: 28 取: 27 取: 26 取: 25 取: 24 取: 23 取: 22 取: 21 取: 20 取: 19 取: 18 取: 17 取: 16 取: 15 取: 14 取: 13 取: 12 取: 11 取: 10 取: 9 取: 8 取: 7 取: 6 取: 5 取: 4 取: 3 取: 2 取: 1 取: 0 存: 1 存: 2 存: 3 存: 4 存: 5 存: 6 存: 7 存: 8 存: 9 存: 10 存: 11 存: 12 存: 13 存: 14 存: 15 存: 16 存: 17 存: 18 存: 19 存: 20 存: 21 存: 22 存: 23 存: 24 存: 25 存: 26 存: 27 存: 28 存: 29 存: 30 存: 31 存: 32 存: 33 存: 34 存: 35 存: 36 存: 37 存: 38 存: 39 存: 40 存: 41 存: 42 存: 43 存: 44 存: 45 存: 46 存: 47 存: 48 存: 49 存: 50 存: 51 存: 52 存: 53 存: 54 存: 55 存: 56 存: 57 存: 58 存: 59 存: 60 存: 61 存: 62 存: 63 存: 64 存: 65 存: 66 存: 67 存: 68 存: 69 存: 70 存: 71 存: 72 存: 73 存: 74 存: 75 存: 76 存: 77 存: 78 存: 79 存: 80 存: 81 存: 82 存: 83 存: 84 存: 85 存: 86 存: 87 存: 88 存: 89 存: 90 存: 91 存: 92 存: 93 存: 94 存: 95 存: 96 存: 97 存: 98 存: 99 存: 100 存: 101 这里是最终的钱数: 101
from multiprocessing import Process, Lock import time def check(i): # 查看不需要上锁 with open('余票') as f: content = f.read() print('第%s个人查到余票还剩%s张' % (i, content)) def buy(i, l): l.acquire() # 拿钥匙,锁门 with open('余票') as f: content = int(f.read()) if content > 0: print('\033[31m 第%s个人买到了票\033[0m' % i) content -= 1 else: print('第%s个人没有抢到' % i) time.sleep(0.1) # 买完票后,把余票数量重写写入数据库的时间延迟 with open('余票', 'w') as f: f.write(str(content)) l.release() if __name__ == '__main__': l = Lock() for i in range(10): # 10个人查票,建立10个查票进程 p_check = Process(target=check, args=(i + 1,)) p_check.start() # 开启进程 for i in range(10): # 10个人抢票,建立10个抢票进程 p_buy = Process(target=buy, args=(i + 1, l)) p_buy.start()
信号量 —— multiprocessing.Semaphore
Lock,属于互斥锁,也就是一把钥匙配备一把锁,同时只允许锁住某一个数据。而信号量则是多把钥匙配备多把锁,也就是说同时允许锁住多个数据。
上述讲的Lock,属于互斥锁,也就是一把钥匙配备一把锁,同时只允许锁住某一个数据。而信号量则是多把钥匙配备多把锁,也就是说同时允许锁住多个数据。
比如在一个粉红发廊,里边有5位服务人员,那么这个发廊最多就同时允许进入5位客人,当又有第6位客人来的时候,就需要在门外等待;当服务人员服务完某位客人后,才允许后续的人再进来一个,换句话说,这个发廊最多同时接待5位客人,多的客人必须等待。
信号量同步基于内部计数器,用户初始化一个计数器初值(比如上述例子中就初始化为5),每调用一次acquire(),计数器减1;每调用一次release(),计数器加1。当计数器为0时,acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器这样的有限资源。信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念
信号量 Semaphore
from multiprocessing import Process, Semaphore import time import random def func(i, sem): sem.acquire() # 获取钥匙 print('第%s个人进入小黑屋,拿了钥匙锁了门' % i) time.sleep(random.randint(3, 5)) print('第%s个人离开小黑屋,还了钥匙,打开了门' % i) sem.release() # 还钥匙 if __name__ == '__main__': sem = Semaphore(4) # 允许4个进程执行 进入小黑屋 # 之后其他人必须等待,有人出来 还了钥匙, 才允许后边的程序进入 for i in range(1,21): p = Process(target=func, args=(i, sem)) # 创建20个禽兽 p.start()
事件 —— multiprocessing.Event
事件处理的机制:全局定义了一个“Flag”(event.is_set()),如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
python中的事件机制,主要用于主进程控制其他进程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”(event.is_set()),如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
is_set:返回全局‘Flag’的bool值
事件 Event
from multiprocessing import Process,Event e = Event() # 默认为False e.set() e.clear() e.wait() e.is_set() # 事件是通过is_set()的bool值,去标识e.wait() 的阻塞状态 # 当is_set()的bool值为False时,e.wait()是阻塞状态 # 当is_set()的bool值为True时,e.wait()是非阻塞状态 # 当使用set()时,是把is_set的bool变为True # 当使用clear()时,是把is_set的bool变为False print(e.is_set())# False wait应该是阻塞住 e.set()# 将is_set 的bool值变为True,将wait变为非阻塞 e.wait() print(e.is_set()) print(123) e.clear() print(e.is_set()) e.wait() print(123)
from multiprocessing import Process,Event import time import random def tra(e): '''信号灯函数''' # e.set() # print('\033[32m 绿灯亮! \033[0m') while 1:# 红绿灯得一直亮着,要么是红灯要么是绿灯 if e.is_set():# True,代表绿灯亮,那么此时代表可以过车 time.sleep(5)# 所以在这让灯等5秒钟,这段时间让车过 print('\033[31m 红灯亮! \033[0m')# 绿灯亮了5秒后应该提示到红灯亮 e.clear()# 把is_set设置为False else: time.sleep(5)# 此时代表红灯亮了,此时应该红灯亮5秒,在此等5秒 print('\033[32m 绿灯亮! \033[0m')# 红的亮够5秒后,该绿灯亮了 e.set()# 将is_set设置为True def Car(i,e): e.wait()# 车等在红绿灯,此时要看是红灯还是绿灯,如果is_set为True就是绿灯,此时可以过车 print('第%s辆车过去了'%i) if __name__ == '__main__': e = Event() triff_light = Process(target=tra,args=(e,))# 信号灯的进程 triff_light.start() for i in range(50):# 描述50辆车的进程 if i % 3 == 0: time.sleep(2) car = Process(target=Car,args=(i+1,e,)) car.start()