再次了解进程与线程
- Python异步与线程:这是21年6月时对线程、进程、同步、异步的简单了解,最近在做毕设时又用到了,打算重新回顾一遍。
多线程与多进程的区别
多线程 threading: 一个人有与异性聊天和看剧两件事要做。单线程的她可以看完剧再去聊天,但这样子可能就没人陪她聊天了「哼,发消息不回」。我们把她看成一个CPU核心,为她开起多线程——先看一会剧,偶尔看看新消息,在两件事(线程)间来回切换。多线程:单个CPU核心可以同时做几件事,不至于卡在某一步傻等着。
用处:爬取网站信息(爬虫),等待多个用户输入
多进程 processing: 一个人有很多砖需要搬,他领取手套、推车各种物资(向系统申请了资源)然后开始搬砖。然而他身边有很多人,我们让这些人去帮他!(一核有难,八核围观)。于是他们做了分工,砖很快就搬完了。多进程让多个CPU核心可以一起做事,不至于只有一人干活而其他人傻站着。
用处:进行高性能计算。只有多进程方案设计合理,才能加速计算。
Python中应用多进程
- 毕设的上位机要接收显示图像,我在udp数据接收解码后,写入IO流,通过管道发给另一个进程,另一个进程做图像的处理与显示,这样就不会因为处理时间过长而阻塞下一次接收解码了。
- multiprocessing — 基于进程的并行:multiprocessing是Python自带的多进程库,这是Python官方的文档对multiprocessing库的介绍。
- Python的线程是操作系统线程,因此要有Python全局解释器锁。一个python解释器进程内有一条主线程,以及多条用户程序的执行线程。即使在多核CPU平台上,由于GIL的存在,所以禁止多线程的并行执行。Python 3.6 才让multiprocessing逐渐发展成一个能用的Python内置多进程库,可以进行进程间的通信,以及有限的内存共享。
两种多进程创建方式
Python多进程可以选择两种创建进程的方式,spawn与fork,实际使用中可以根据子进程具体做什么来选取用fork还是spawn。
- fork:除了必要的启动资源外,其他变量,包,数据等都继承自父进程,并且是copy-on-write的,也就是共享了父进程的一些内存页,因此启动较快,但是由于大部分都用的父进程数据,所以是不安全的进程
- spawn:从头构建一个子进程,父进程的数据等拷贝到子进程空间内,拥有自己的Python解释器,所以需要重新加载一遍父进程的包,因此启动较慢,由于数据都是自己的,安全性较高
1 | multiprocessing.set_start_method('spawn') # default on WinOS or MacOS |
四种进程间通信方式
Python中进程间通信可以采用进程池Pool、管道Pipe、队列Queue,在新版本Python中多了共享内存Manager的方式。
- 进程池Pool:不怎么使用?通常使用另外两个方式通信
- 管道和队列:Queue用于多个进程间实现通信,Pipe是两个进程的通信。Queue通过put和get方法插入读取队列,Pipe通过send和recv方法发送和接收信息,用法可以参考这篇文章:python进程间通信,如果追求运行更快,那么最好使用管道Pipe而队列Queue,详细查看Python pipes and queues performance
- 共享内存Manager:Pipe Queue 把需要通信的信息从内存里深拷贝了一份给其他线程使用(需要分发的线程越多,其占用的内存越多)。而共享内存会由解释器负责维护一块共享内存(而不用深拷贝),这块内存每个进程都能读取到,读写的时候遵守管理(因此不要以为用了共享内存就一定变快)
编程中要注意
为了避免自己调用自己时重复执行主进程,**多进程的主进程一定要写在程序入口if __name__ == ‘__main__’:,否则可能会报错。
1 | def function1(id): # 这里是子进程 |
设计高性能的多进程时,遵守以下规则:
- 尽可能少传一点数据
- 尽可能减少主线程的负担
- 尽可能不让某个进程傻等着
- 尽可能减少进程间通信的频率
网上一些好的文档
- Python程序入口 name == ‘main‘ 有重要功能(多线程)而非编程习惯:这篇文章讲述了Python中程序入口的作用,Python用这个简单的方法来判断当前的模块是被直接运行还是被调用,这是很重要的功能。
- 在Python中优雅地用多进程