Python并发编程-多进程
Python并发编程-多进程
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.多进程相关概念
由于Python的GIL全局解释器锁存在,多线程未必是CPU密集型程序的好的选择。
多进程可以完全独立的进程环境中运行程序,可以较充分地利用多处理器。
但是进程本身的隔离带来的数据不共享也是一个问题。而且线程比进程轻量级。
二.multiprocessing
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import multiprocessing 7 import datetime 8 9 """ 10 Process类遵循了Thread类的API,减少了学习难度。 11 12 相比线程的方法,进程多出来几个方法需要注意下: 13 pid: 14 进程id 15 exitcode: 16 进程的退出状态码 17 terminate(): 18 终止指定的进程 19 20 先看一个例子,前面介绍的单线程、多线程比较的例子的多进程版本,具体代码如下所示。 21 """ 22 23 def calc(i): 24 sum = 0 25 for _ in range(1000000000): 26 sum += 1 27 return i,sum 28 29 if __name__ == '__main__': 30 start = datetime.datetime.now() 31 32 ps = [] 33 34 for i in range(4): 35 p = multiprocessing.Process(target=calc,args=(i,),name="calc-{}".format(i)) 36 ps.append(p) 37 p.start() 38 39 for p in ps: 40 p.join() 41 print(p.name,p.exitcode) 42 43 delta = (datetime.datetime.now() - start).total_seconds() 44 print(delta) 45 46 for p in ps: 47 print(p.name,p.exitcode) 48 49 print("=== end ===")
calc-0 0 calc-1 0 calc-2 0 calc-3 0 47.011848 calc-0 0 calc-1 0 calc-2 0 calc-3 0 === end ===
三.进程间同步
Python在进程间同步提供了和线程同步一样的类,使用的方法一样,使用的效果也类似。
不过,进程间代价要高于线程间,而且系统底层实现是不同的,只不过Python屏蔽了这些不同之处,让用户简单使用多进程。
multiprocessing还提供共享内存、服务器进程来共享数据,还提供了用于进程间通讯的Queue队列,Pipe管道。
通信方式不同 1.多进程就是启动多个解释器进程,进程间通信必须序列化、反序列化 2.数据的线程安全性问题
如果每个进程中没有实现多线程,即每个进程仅有一个线程,此时GIL可以说没什么用了。
四.进程池举例
1>.multiprocessing.Pool 是进程池类
multiprocessing.Pool 是进程池类,他有很多方法,具体如下: apply(self, func, args=(), kwds={}): 阻塞执行,导致主进程执行其他子进程就像一个个执行
apply_async(self, func, args=(), kwds={},callback=None, error_callback=None): 与apply方法用法一致,非阻塞异步执行,得到结果后会执行回调
close(): 关闭池,池不能再接受新的任务,所有任务完成后退出进程
terminate():
立即结束工作进程,不再处理未处理的任务
join(): 主进程阻塞等待子进程的退出, join方法要在close或terminate之后使用
2>.同步调用
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import logging 7 import datetime 8 import multiprocessing 9 # 日志打印进程id、进程名、线程id、线程名 10 logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s") 11 def calc(i): 12 sum = 0 13 for _ in range(1000000000): # 10亿 14 sum += 1 15 logging.info(sum) 16 return i,sum # 进程要return,才可以拿到这个结果 17 18 19 if __name__ == '__main__': # 注意一定要有这一句 20 start = datetime.datetime.now() 21 22 pool = multiprocessing.Pool(4) 23 24 for i in range(4): 25 # 返回值,同步调用,注意观察CPU使用 26 ret = pool.apply(calc, args=(i,)) 27 print(ret) 28 pool.close() 29 pool.join() 30 31 delta = (datetime.datetime.now() - start).total_seconds() 32 print(delta) 33 print('===end====')
calc-0 0 calc-1 0 calc-2 0 calc-3 0 50.399442 calc-0 0 calc-1 0 calc-2 0 calc-3 0 === end ===
3>.异步调用
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import logging 7 import datetime 8 import multiprocessing 9 # 日志打印进程id、进程名、线程id、线程名 10 logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s") 11 12 def calc(i): 13 sum = 0 14 for _ in range(1000000000): # 10亿 15 sum += 1 16 logging.info(sum) 17 return i, sum # 进程要return,callback才可以拿到这个结果 18 19 if __name__ == '__main__': 20 start = datetime.datetime.now() # 注意一定要有这一句 21 pool = multiprocessing.Pool(4) 22 for i in range(4): 23 # 异步拿到的返回值是什么? 24 ret = pool.apply_async(calc, args=(i,)) 25 print(ret, '~~~~~~~') # 异步,如何拿到真正的结果呢? 26 pool.close() 27 pool.join() 28 delta = (datetime.datetime.now() - start).total_seconds() 29 print(delta) 30 print('===end====')
<multiprocessing.pool.ApplyResult object at 0x000001F02D3230C8> ~~~~~~~ <multiprocessing.pool.ApplyResult object at 0x000001F02D3231C8> ~~~~~~~ <multiprocessing.pool.ApplyResult object at 0x000001F02D323308> ~~~~~~~ <multiprocessing.pool.ApplyResult object at 0x000001F02D3233C8> ~~~~~~~ 5828 SpawnPoolWorker-4 7608 1000000000 7248 SpawnPoolWorker-3 5168 1000000000 2020 SpawnPoolWorker-1 2252 1000000000 1620 SpawnPoolWorker-2 3128 1000000000 47.058129 ===end====
4>.异步调用,拿最终结果
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import logging 7 import datetime 8 import multiprocessing 9 10 # 日志打印进程id、进程名、线程id、线程名 11 logging.basicConfig(level=logging.INFO, format="%(process)d %(processName)s %(thread)d %(message)s") 12 13 14 def calc(i): 15 sum = 0 16 for _ in range(1000000000): # 10亿 17 sum += 1 18 logging.info(sum) 19 return i, sum # 进程要return,callback才可以拿到这个结果 20 21 if __name__ == '__main__': 22 start = datetime.datetime.now() # 注意一定要有这一句 23 24 pool = multiprocessing.Pool(4) 25 26 for i in range(4): 27 #异步拿到的返回值是什么?回调起了什么作用? 28 ret = pool.apply_async(calc, args=(i,),callback=lambda ret: logging.info('{} in callback'.format(ret))) 29 print(ret, '~~~~~~~') 30 31 pool.close() 32 pool.join() 33 delta = (datetime.datetime.now() - start).total_seconds() 34 print(delta) 35 print('===end====')
<multiprocessing.pool.ApplyResult object at 0x0000017D9709F308> ~~~~~~~ <multiprocessing.pool.ApplyResult object at 0x0000017D9709F448> ~~~~~~~ <multiprocessing.pool.ApplyResult object at 0x0000017D9709F508> ~~~~~~~ <multiprocessing.pool.ApplyResult object at 0x0000017D9709F5C8> ~~~~~~~ 8964 SpawnPoolWorker-1 1620 1000000000 572 MainProcess 8184 (0, 1000000000) in callback 992 SpawnPoolWorker-2 2484 1000000000 572 MainProcess 8184 (1, 1000000000) in callback 10608 SpawnPoolWorker-3 2404 1000000000 572 MainProcess 8184 (2, 1000000000) in callback 3268 SpawnPoolWorker-4 8684 1000000000 572 MainProcess 8184 (3, 1000000000) in callback 44.991332 ===end====
五.多进程、多线程的选择
1>.CPU密集型
CPython中使用到了GIL,多线程的时候锁相互竞争,且多核优势不能发挥,选用Python多进程效率更高。
2>.IO密集型
在Python中适合是用多线程,可以减少多进程间IO的序列化开销。且在IO等待的时候,切换到其他线程继续执行,效率不错。
3>.应用
请求/应答模型:WEB应用中常见的处理模型
master启动多个worker工作进程,一般和CPU数目相同。发挥多核优势。
worker工作进程中,往往需要操作网络IO和磁盘IO,启动多线程,提高并发处理能力。worker处理用户的请求,往往需要等待数据,处理完请求还要通过网络IO返回响应。
这就是nginx工作模式。
六.Linux的特殊进程(在Linux/Unix中,通过父进程创建子进程。)
1>.僵尸进程
一个进程使用了fork创建了子进程,如果子进程终止进入僵死状态,而父进程并没有调用wait或者waitpid获取子进程的状态信息,那么子进程仍留下一个数据结构保存在系统中,这种进程称为僵尸进程。
僵尸进程会占用一定的内存空间,还占用了进程号,所以一定要避免大量的僵尸进程产生。有很多方法可以避免僵尸进程。
2>.孤儿进程
父进程退出,而它的子进程仍在运行,那么这些子进程就会成为孤儿进程。孤儿进程会被init进程(进程号为1)收养,并由init进程对它们完成状态收集工作。
init进程会循环调用wait这些孤儿进程,所以,孤儿进程没有什么危害。
3>.守护进程
它是运行在后台的一种特殊进程。它独立于控制终端并周期性执行某种任务或等待处理某些事件。
守护进程的父进程是init进程,因为其父进程已经故意被终止掉了。
守护进程相对于普通的孤儿进程需要做一些特殊处理。
本文来自博客园,作者:尹正杰,转载请注明原文链接:https://www.cnblogs.com/yinzhengjie/p/11894972.html,个人微信: "JasonYin2020"(添加时请备注来源及意图备注,有偿付费)
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。