多进程的含义
进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
顾名思义,多进程就是启用多个进程同时运行。由于进程是线程的集合,而且进程是由一个或多个线程构成的,所以多进程的运行意味着有大于或等于进程数量的线程在运行。
Python 多进程的优势
通过上一课时我们知道,由于进程中 GIL 的存在,Python 中的多线程并不能很好地发挥多核优势,一个进程中的多个线程,在同一时刻只能有一个线程运行。
而对于多进程来说,每个进程都有属于自己的 GIL,所以,在多核处理器下,多进程的运行是不会受 GIL 的影响的。因此,多进程能更好地发挥多核的优势。
当然,对于爬虫这种 IO 密集型任务来说,多线程和多进程影响差别并不大。对于计算密集型任务来说,Python 的多进程相比多线程,其多核运行效率会有成倍的提升。
总的来说,Python 的多进程整体来看是比多线程更有优势的。所以,在条件允许的情况下,能用多进程就尽量用多进程。
不过值得注意的是,由于进程是系统进行资源分配和调度的一个独立单位,所以各个进程之间的数据是无法共享的,如多个进程无法共享一个全局变量,进程之间的数据共享需要有单独的机制来实现,这在后面也会讲到。
多进程的实现
在 Python 中也有内置的库来实现多进程,它就是 multiprocessing。
multiprocessing 提供了一系列的组件,如 Process(进程)、Queue(队列)、Semaphore(信号量)、Pipe(管道)、Lock(锁)、Pool(进程池)等,接下来让我们来了解下它们的使用方法。
直接使用 Process 类
在 multiprocessing 中,每一个进程都用一个 Process 类来表示。它的 API 调用如下:
Process([group [, target [, name [, args [, kwargs]]]]])
- target 表示调用对象,你可以传入方法的名字。
- args 表示被调用对象的位置参数元组,比如 target 是函数 func,他有两个参数 m,n,那么 args 就传入 [m, n] 即可。
- kwargs 表示调用对象的字典。
- name 是别名,相当于给这个进程取一个名字。
- group 分组。
我们先用一个实例来感受一下:
import multiprocessing
def process(index): print(f'Process: {index}')
if name == 'main': for i in range(5): p = multiprocessing.Process(target=process, args=(i,)) p.start() </code></pre>
这是一个实现多进程最基础的方式:通过创建 Process 来新建一个子进程,其中 target 参数传入方法名,args 是方法的参数,是以元组的形式传入,其和被调用的方法 process 的参数是一一对应的。
注意:这里 args 必须要是一个元组,如果只有一个参数,那也要在元组第一个元素后面加一个逗号,如果没有逗号则和单个元素本身没有区别,无法构成元组,导致参数传递出现问题。
创建完进程之后,我们通过调用 start 方法即可启动进程了。运行结果如下:
Process: 0
Process: 1
Process: 2
Process: 3
Process: 4
可以看到,我们运行了 5 个子进程,每个进程都调用了 process 方法。process 方法的 index 参数通过 Process 的 args 传入,分别是 0~4 这 5 个序号,最后打印出来,5 个子进程运行结束。
由于进程是 Python 中最小的资源分配单元,因此这些进程和线程不同,各个进程之间的数据是不会共享的,每启动一个进程,都会独立分配资源。
另外,在当前 CPU 核数足够的情况下,这些不同的进程会分配给不同的 CPU 核来运行,实现真正的并行执行。
multiprocessing 还提供了几个比较有用的方法,如我们可以通过 cpu_count 的方法来获取当前机器 CPU 的核心数量,通过 active_children 方法获取当前还在运行的所有进程。
下面通过一个实例来看一下:
import multiprocessing
import time
def process(index): time.sleep(index) print(f'Process: {index}')
if name == 'main': for i in range(5): p = multiprocessing.Process(target=process, args=[i]) p.start() print(f'CPU number: {multiprocessing.cpu_count()}') for p in multiprocessing.active_children(): print(f'Child process name: {p.name} id: {p.pid}') print('Process Ended') </code></pre>
运行结果如下:
Process: 0
CPU number: 8
Child process name: Process-5 id: 73595
Child process name: Process-2 id: 73592
Child process name: Process-3 id: 73593
Child process name: Process-4 id: 73594
Process Ended
Process: 1
Process: 2
Process: 3
Process: 4
在上面的例子中我们通过 cpu_count 成功获取了 CPU 核心的数量:8 个,当然不同的机器结果可能不同。
另外我们还通过 active_children 获取到了当前正在活跃运行的进程列表。然后我们遍历了每个进程,并将它们的名称和进程号打印出来了,这里进程号直接使用 pid 属性即可获取,进程名称直接通过 name 属性即可获取。
以上我们就完成了多进程的创建和一些基本信息的获取。
继承 Process 类
在上面的例子中,我们创建进程是直接使用 Process 这个类来创建的,这是一种创建进程的方式。不过,创建进程的方式不止这一种,同样,我们也可以像线程 Thread 一样来通过继承的方式创建一个进程类,进程的基本操作我们在子类的 run 方法中实现即可。
通过一个实例来看一下:
from multiprocessing import Process
import time
class MyProcess(Process): def init(self, loop): Process.init(self) self.loop = loop
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-keyword">for</span> count <span class="hljs-keyword">in</span> range(self.loop): time.sleep(<span class="hljs-number">1</span>) print(<span class="hljs-string">f'Pi