简介
本片文章主要对python的多进程进行介绍,其中常用的多进程模块其实与多线程模块的使用方式以及相关方法都大体类似,而且多进程和多线程也有一些相似性,如果对多线程有所了解的话,是比较方便上手的。
GIL
GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。
在线程中,所有线程公用这个一个资源,只有获得这个GIL,线程才会去执行,这也就导致了python 程序运行速度慢的一种原因,除此之外,请注意:多核多线程比单核多线程更差,原因是单核下的多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。
因此很多使用python 的老手表达出使用python如果想要速度快就用多进程的方式的意思,那是因为每个进程拥有单独的GIL,可以互不影响的单独运行,可以充分的利用多核的优势。
进程与线程
我个人其实对编程这块是不怎么熟悉的,之前虽然也有一直写个脚本或者程序来帮忙解决一些工作上的问题,但是都是浅尝辄止,能实现就好,现在也算是来补课了。
根据网上很多资料所说的,多进程使用与CPU密集性,而多线程适用于IO密集型(网络爬虫,文件输入等),其实在实际使用中,除非真的是爬取大量数据和网页这种,或者不停的获取数据,插入,查询等行为比较适合多线程,其他的大部分情况下还是进程更占一些优势,实在不知道怎么选,就用多进程好了。
多进程实现
Process 类
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
Process是用来创建子进程的,与threading模块的Thread,用法之类的都挺像的,在这个类需要的参数中,主要也是关注target 以及args参数,target 传入参数名,args传入目标函数需要的参数,类型为元组。
其实现方式同样有两种,第一种是创建Process 对象来执行,另一种就是重写run方法来实多进程
代码示例
from multiprocessing import Process
def test():
for i in range(10):
print(i)
if __name__ == '__main__':
pro = Process(target=test, daemon=True)
pro.start()
pro.join()
简单的写一个示例,其中需要注意,多进程的代码部分要写道代码
“if name == ‘main’:”内,不然会报错。
我要是这么写就会报错
错误为
意思应该就是多进程的部分放到那段代码下面。
重写run方法代码
不建议用这个,因为感觉不是太清晰
class MyProcess(Process):
def __init__(self, value):
self.value = value
def run(self):
for i in range(self.value):
print(i)
if __name__ == '__main__':
a = MyProcess(10)
a.run()
join()与daemon属性
class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
Process 类的daemon 属性的作用,
-
daemon = True: 主进程运行完不会检查子进程的状态(是否执行完),直接结束进程;
-
daemon = False: 主进程运行完先检查子进程的状态(是否执行完),子进程执行完后,直接结束进程;
某种程度上将,跟join函数有类似但是又不同,join()函数是阻塞主进程的执行,先执行子进程,执行完之后再继续执行主进程,直至结束,两者都在某种意义上保证了子进程完整的完成执行,只不过适用场景肯定是有所不同的,后续可能过来补充…
进程间数据共享
在多进程中,每一个进程都有自己的变量拷贝,所以主进程中的一个变量传入其他进程修改,得到的结果仍然存储于那个进程中,主进程中这个变量其实相当于没有被修改过。为了能让其他进程的修改能够同步到主进程上来,需要创建能在多个进程之间共享的变量。
Python中进程间共享数据,除了基本的queue,pipe和value+array外,还提供了更高层次的封装。使用multiprocessing.Manager可以简单地使用这些高级接口。
Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问。从而达到多进程间数据通信且安全。
Manager支持的类型有list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array。
代码示例
from multiprocessing import Process, Manager
def f1(ns, l):
ns.x += 1
l.append(2)
def f2(ns, l):
ns.x -= 2
l.append(3)
if __name__ == '__main__':
manager = Manager()
ns = manager.Namespace()
l = manager.list([1])
ns.x = 0
p1 = Process(target=f1, args=(ns, l))
p2 = Process(target=f2, args=(ns, l))
p1.start()
p2.start()
p1.join()
p2.join()
print(ns, l)
-------------------------------------------------------------
结果
Namespace(x=-1) [1, 2, 3]
注意事项
有时候使用manager仍会发现变量没有被其他进程改变,比如使用manager.Namespace()创建列表修改无效,或manager.list()创建多层列表时里面列表中的元素修改无效。这是因为它们是可变对象,修改时内存地址不变,于是主进程还是读取原来的地址,
参考
https://blog.csdn.net/weixin_35544490/article/details/113503238
https://www.jianshu.com/p/94a1caf21720
https://blog.csdn.net/xiemanR/article/details/71700531
https://blog.csdn.net/weixin_43751285/article/details/92837030
https://www.pianshen.com/article/8608860958/
https://www.cnblogs.com/Darren-Dai/p/8551813.html
https://zhuanlan.zhihu.com/p/34269037
https://segmentfault.com/a/1190000018619281
https://blog.csdn.net/qq_36653505/article/details/85254909