0%

进程与线程

再次了解进程与线程

  • 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。

  1. fork:除了必要的启动资源外,其他变量,包,数据等都继承自父进程,并且是copy-on-write的,也就是共享了父进程的一些内存页,因此启动较快,但是由于大部分都用的父进程数据,所以是不安全的进程
  2. spawn:从头构建一个子进程,父进程的数据等拷贝到子进程空间内,拥有自己的Python解释器,所以需要重新加载一遍父进程的包,因此启动较慢,由于数据都是自己的,安全性较高
1
2
multiprocessing.set_start_method('spawn')  # default on WinOS or MacOS
multiprocessing.set_start_method('fork') # default on Linux (UnixOS)

四种进程间通信方式

Python中进程间通信可以采用进程池Pool、管道Pipe、队列Queue,在新版本Python中多了共享内存Manager的方式。

  1. 进程池Pool:不怎么使用?通常使用另外两个方式通信
  2. 管道和队列:Queue用于多个进程间实现通信,Pipe是两个进程的通信。Queue通过put和get方法插入读取队列,Pipe通过send和recv方法发送和接收信息,用法可以参考这篇文章:python进程间通信,如果追求运行更快,那么最好使用管道Pipe而队列Queue,详细查看Python pipes and queues performance
  3. 共享内存Manager:Pipe Queue 把需要通信的信息从内存里深拷贝了一份给其他线程使用(需要分发的线程越多,其占用的内存越多)。而共享内存会由解释器负责维护一块共享内存(而不用深拷贝),这块内存每个进程都能读取到,读写的时候遵守管理(因此不要以为用了共享内存就一定变快)

编程中要注意

为了避免自己调用自己时重复执行主进程,**多进程的主进程一定要写在程序入口if __name__ == ‘__main__’:,否则可能会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
def function1(id):  # 这里是子进程
print(f'id {id}')

def run__process(): # 这里是主进程
from multiprocessing import Process
process = [mp.Process(target=function1, args=(1,)),
mp.Process(target=function1, args=(2,)), ]
[p.start() for p in process] # 开启了两个进程
[p.join() for p in process] # 等待两个进程依次结束

# run__process() # 主线程不建议写在 if外部。由于这里的例子很简单,你强行这么做可能不会报错
if __name__ =='__main__':
run__process() # 正确做法:主线程只能写在 if内部

设计高性能的多进程时,遵守以下规则:

  • 尽可能少传一点数据
  • 尽可能减少主线程的负担
  • 尽可能不让某个进程傻等着
  • 尽可能减少进程间通信的频率

网上一些好的文档