Part 4系统编程之进程---进程同步
(一)简介
通过之前的学习,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用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 running 19 0: 1448 is running 20 2: 1096 is running 21 2:1096 is done 22 0:1448 is done 23 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 running 23 0:17468 is done 24 2: 16688 is running 25 2:16688 is done 26 1: 15984 is running 27 1:15984 is done 28 3: 15828 is running 29 3:15828 is done 30 4: 18156 is running 31 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 running 20 0:17348 is done 21 1: 18164 is running 22 1:18164 is done 23 2: 18160 is running 24 2:18160 is done 25 3: 17652 is running 26 3:17652 is done 27 4: 8340 is running 28 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'] -= 1 21 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购票失败
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。