(一)简介
通过之前的学习,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题:当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。例如下列情形--多进程抢占输出资源:
1 import os 2 import time 3 import random 4 from multiprocessing import Process 5 6 def work(n): 7 print('%s: %s is running' %(n,os.getpid())) 8 time.sleep(random.random()) 9 print('%s:%s is done' %(n,os.getpid()))10 11 if __name__ == '__main__':12 for i in range(3):13 p=Process(target=work,args=(i,))14 p.start()15 16 17 》》》输出:18 1: 16548 is running19 0: 1448 is running20 2: 1096 is running21 2:1096 is done22 0:1448 is done23 1:16548 is done
可以看到我们的输出结果是乱的,如果想有序的执行,先run再done,怎么办?
这时,就要用到我们今天的内容,锁!
(二)创建锁
首先我们要引入Lock,接着在进程开始和结束分别给取得锁和释放锁,代码如下:
1 import os 2 import time 3 import random 4 from multiprocessing import Lock 5 from multiprocessing import Process 6 7 def work(n,lock): 8 lock.acquire() #取得锁 9 print('%s: %s is running' %(n,os.getpid()))10 time.sleep(random.random())11 print('%s:%s is done' %(n,os.getpid()))12 lock.release() #释放锁13 14 if __name__ == '__main__':15 lock = Lock() #创建锁16 for i in range(5):17 p=Process(target=work,args=(i,lock))18 p.start()19 20 21 》》》输出:22 0: 17468 is running23 0:17468 is done24 2: 16688 is running25 2:16688 is done26 1: 15984 is running27 1:15984 is done28 3: 15828 is running29 3:15828 is done30 4: 18156 is running31 4:18156 is done
从结果上看,每个进程都是先开始再结束,尽管进程执行的顺序是无序的
(三)锁的原理
我们用下图来解释:
理论上来讲,进程一般是异步的
但是加了锁之后,就变成同步了但进程执行的顺序为什么是无序的呢?这就要看谁先拿到钥匙了,优先者满足以下2个条件:
1.操作系统先响应的进程2.当时没有时间片轮询,刚好就是它那如何做到有序执行进程呢?就用之前说的join,他会阻塞进程,使进程串行执行。
1 import os 2 import time 3 import random 4 from multiprocessing import Process 5 6 def work(n): 7 print('%s: %s is running' %(n,os.getpid())) 8 time.sleep(random.random()) 9 print('%s:%s is done' %(n,os.getpid()))10 11 if __name__ == '__main__':12 for i in range(5):13 p=Process(target=work,args=(i,))14 p.start()15 p.join()16 17 18 》》》输出:19 0: 17348 is running20 0:17348 is done21 1: 18164 is running22 1:18164 is done23 2: 18160 is running24 2:18160 is done25 3: 17652 is running26 3:17652 is done27 4: 8340 is running28 4:8340 is done
所以我们从结果可以看到锁和join的区别就在于此:
锁执行时是无序的,join是有序的
由并发变成了串行,牺牲了运行效率,但避免了竞争,却保证了数据的安全。
(四)实例演示
总结一下之前的内容:
同步控制:只要用到了锁 锁之间的代码就会变成同步的
锁 :控制一段代码 同一时间 只能被一个进程执行接下来,我们简单演示一下12306抢票中锁的应用,我们知道抢票分为一下几步:
用户发送买票请求》》收到请求后在数据库读取数据》》如果还有票就在数据库减少一张票》》告知用户抢票成功
我们在代码中会模拟两次延时,分别是读取延时和写入延时,这是因为服务器和数据库不在同一台机器上,他们之间交互数据,必然有延时。
代码如下:
1 import time 2 import random 3 from multiprocessing import Process 4 from multiprocessing import Lock 5 import json 6 7 8 def buy_ticket(i, lock): 9 #取得锁保证数据共享的单一性,避免错误10 lock.acquire()11 #读取文件12 with open('ticket') as f:13 #反序列化,将字符串转换为字典14 tick_count = json.load(f)15 #模拟读取延时16 time.sleep(random.random())17 #判断余票并进行相应操作18 if tick_count['count'] > 0:19 print('person%s购票成功'%i)20 tick_count['count'] -= 121 else:22 print('余票不足,person%s购票失败'%i)23 #将信息写入文件24 with open('ticket', 'w') as f:25 json.dump(tick_count,f)26 #模拟写入延时27 time.sleep(random.random())28 #释放锁29 lock.release()30 31 32 if __name__ == '__main__':33 lock = Lock() #创建锁34 for i in range(5): # 模拟5个用户抢票35 Process(target=buy_ticket, args=(i, lock)).start()36 37 》》》输出:38 person0购票成功39 余票不足,person1购票失败40 余票不足,person2购票失败41 余票不足,person3购票失败42 余票不足,person4购票失败
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。