python(一):multiprocessing——死锁

前言
近年来,使用python的人愈来愈多,这得益于其清晰的语法、低廉的入门代价等因素。尽管python受到的关注日益增多,但python并不是完美,例如被人诟病最多的GIL(值得注意的是,GIL并不是python特性,它是在实现Python解析器(CPython)时所引入的一个概念,而CPython是大部分环境下默认的Python执行环境),全称Global Interpreter Lock。从官方定义来看,GIL无疑就是一把全局排他锁,会严重影响python多线程的效率,甚至几乎等于Python是个单线程程序。python

为了知足开发者的需求,python社区推出了multiprocessing。顾名思义,multiprocessing使用了多进程而不是多线程,每一个进程有本身的独立的GIL,所以也不会出现进程之间的GIL争抢。固然multiprocessing也并不是完美,例如增长了数据通信的难度等方面。讲了这么多背景,下面分享一下使用multiprocessing踩过的坑。因为这篇博客偏向实际工程,主要分享应用经验,相关基础知识能够查阅Python Documentation。多线程

系统
>>> import sys
>>> print(sys.version)
3.6.0 |Anaconda 4.3.1 (64-bit)| (default, Dec 23 2016, 12:22:00) \n[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)]
1
2
3
死锁
百度百科对死锁的定义优化

死锁是指两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程1。this

从定义可知,永远互相等待是死锁的一个重要特征。在multiprocessing中,你稍不留神,也会犯这种错误,例如:.net

from multiprocessing import Process, Queue线程

def f(q):
q.put('X' * 1000000)blog

if __name__ == '__main__':
queue = Queue()
p = Process(target=f, args=(queue,))
p.start()
p.join() # this deadlocks
1
2
3
4
5
6
7
8
9
10
结果是死锁。当一个进程被join时,Python会检查被放入Queue中的数据是否已经所有删除(例如Queue.get),若没有删除,则进程会一直处于等待状态。发现这种状况时,一方面感叹“你让我找的好苦啊”,另外一方面思考python的开发者怎么会对这种状况坐视不理呢?是否作了某些尝试?例如若Queue小于某个阈值,进程join会将其视为空Queue。基于这种猜测,作了如下实验进程

from multiprocessing import Process, Queue
import timeip

def f(q):
num = 10000
q.put('X' * num)
print("Finish put....")资源

if __name__ == '__main__':
queue = Queue()
p = Process(target=f, args=(queue,))
p.start()
print("Start to sleep...")
time.sleep(2)
print("Wake up....")
p.join()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
结果是程序正常结束,必定程度上验证了个人猜测。为了进一步肯定猜测的正确性,我又作了num=1000、num=100和num=1的实验,结果均是程序正常结束,证实进程join时的确会判断Queue的大小,从而避免死锁。尽管这种策略有必定效果,但并不能根治死锁,因此进程join时必定要保证Queue中数据已经被所有取走。

除了上述的状况外,进程join自身、终止带锁的进程等状况也会致使死锁,之后会慢慢分享给你们。

相关文章
相关标签/搜索