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购票失败
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。

 

posted @ 2018-10-02 15:44  A-handsome-cxy  阅读(211)  评论(0编辑  收藏  举报