# Create a new process with a specific function that has an error# to execute with args.defexample(args):
split_args =list(args.split())# "name" variable is not in the function namespace - should raise errorreturn name
# New process
process = Process(target=example, args=("Hi",))# Running the new process
process.start()# Check process is running
process.is_alive()"""
True
Process Process-15:
Traceback (most recent call last):
File "/usr/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
self.run()
"""技术支持
Process 对象还提供了 terminate() 和 kill() 方法,使用户可以强制终止进程。
# Create a new process with a specific function to execute with args.defexample(args):
split_args =list(args.split())# "name" variable is not in the function namespace - should raise errorreturn split_args
# New process
process = Process(target=example, args=("Hi",))# Running the new process
process.start()if process.is_alive():
process.terminate()# You can also use process.kill()print("Process terminated forcefully")"""
Process terminated forcefully
"""技术支持
import time
defdo_something():print("I'm going to sleep")
time.sleep(1)print("I'm awake")技术支持
第一步是创建一个新进程:我们将创建两个。
# Create new child process
process_1 = Process(target=do_something)
process_2 = Process(target=do_something)技术支持
在笔记本环境中,并发程序看起来如下:
%%time
# Starts both processes
process_1.start()
process_2.start()"""
I'm going to sleep
CPU times: user 810 µs, sys: 7.34 ms, total: 8.15 ms
Wall time: 6.04 ms
I'm going to sleep
"""技术支持
%%time
# Create new child process (Cannot run a process more than once)
new_process_1 = Process(target=do_something)
new_process_2 = Process(target=do_something)
# Starts both processes
new_process_1.start()
new_process_2.start()
new_process_1.join()
new_process_2.join()"""
I'm going to sleep
I'm going to sleep
I'm awake
I'm awake
CPU times: user 0 ns, sys: 14 ms, total: 14 ms
Wall time: 1.01 s
"""技术支持
问题解决。
本次运行的 Wall time 比第一次稍长。然而,这次运行在返回时间信息之前,已完成对两个目标函数的调用。我们也可以据此思路,让更多进程并发运行。
Python 的标准库内置了多个软件包,让开发者可以立即获得这门语言的诸多便利。
其中一个就是 multiprocessing 模块,它使系统能够同时运行多个进程。换句话说,开发者可以将应用程序拆分为可独立于其 Python 代码运行的更小的线程。这些线程或进程随后由操作系统分配到处理器上,从而并行运行,提升您的 Python 程序的性能与效率。
如果您对线程、进程、处理器等词汇还不熟悉,也不用担心。本文将介绍什么是进程、它与线程有何不同,以及如何使用
multiprocessing模块。什么是 Python 中的进程?
理解进程与线程的概念,对于更好地把握操作系统在各个执行阶段如何管理程序非常有帮助。进程本质上是对一个计算机程序的引用。每个程序都对应一个进程。
在您阅读这篇文章时,即使只打开了少数几个程序,您的计算机也很可能同时运行着许多进程——这是因为大多数操作系统都会在后台运行多个任务。比如,您当前也许只运行了三个程序,但计算机可能同时有超过 30 个活动进程在运行。
如何查看计算机上当前的活动进程取决于您的操作系统:
Python 可以访问真实的系统级进程。通过从 multiprocessing 模块实例化
Process类的实例,开发者可以使用 Python 引用底层的原生进程。当一个进程启动时,会在幕后创建一个新的原生进程。一个 Python 进程的生命周期包含三个阶段:新建进程、运行中的进程和已终止的进程——我们将逐一介绍。所有进程都由一个或多个线程构成。还记得我们提到过“进程只是对计算机程序的引用”吗?每个 Python 程序就是一个包含一个默认线程(称为主线程)的进程。主线程负责执行您 Python 程序中的指令。不过,需要注意的是,进程与线程并不相同。
多进程 vs. 多线程
更具体地说,一个 Python 解释器实例——即将 Python 代码转换为计算机可理解语言的工具——等同于一个进程。一个进程至少包含一个线程,在 Python 中称为“主线程”,当然也可以在同一进程内创建其他线程——在一个进程中创建的所有其他线程都归属于该进程。
线程用于表示您的 Python 程序将如何被执行,一旦所有非后台线程都结束,Python 进程也会终止。
Python 提供了两个非常相似的类,使我们能够更好地控制进程与线程:multiprocessing.Process 和 threading.Thread。
下面回顾它们的一些相似点与不同点。
相似点
并发
并发是一种概念,指程序的不同部分可以以乱序或部分有序的方式执行,而不影响最终结果。这两个类最初就是为了并发而设计的。
支持并发原语
multiprocessing.Process 和 threading.Thread 类支持相同的并发原语——这是一类用于同步和协调线程与进程的工具。
统一的 API
一旦您掌握了 multiprocessing.Process 的 API,便可将该知识迁移到 threading.Thread 的 API,反之亦然。它们的设计本就有意保持一致。
不同点
功能
尽管它们的 API 相同,但进程与线程并不相同。进程是比线程更高层的抽象:进程是对一个计算机程序的引用,而线程隶属于进程。这种差异体现在类本身上。因此,这两个类代表了由底层操作系统管理的两种不同的原生功能。
对共享状态的访问
这两个类访问共享状态的方式不同。由于线程隶属于某个进程,它们可以在进程内共享内存。因此,在新线程中执行的函数仍然可以访问进程内相同的数据与状态。线程之间共享状态的方式称为“共享内存”,实现起来相对直接。相反,进程之间共享状态则要复杂得多:状态必须被序列化并在进程之间传输。换句话说,进程不通过共享内存共享状态,因为它们拥有各自独立的内存。取而代之,进程间的共享通过一种称为“进程间通信”(inter-process communication)的技术实现,而在 Python 中执行它需要使用诸如 multiprocessing.Pipe 或 multiprocessing.Queue 等显式工具。
GIL
Python 的全局解释器锁(GIL)是一种锁,它只允许一个线程持有对 Python 解释器的控制权。多个线程受制于 GIL,这常常使得在 CPython 解释器上使用 Python 进行多线程以实现真正的多核执行变得不切实际:Python 并不支持通过多线程实现真正的多核并行。不过,进程不受 GIL 的限制,因为 GIL 作用于每个 Python 进程内部,而不是跨进程。
在使用场景方面,当程序为 CPU 密集型且不需要进行 IO 或用户交互时,多进程往往优于多线程。对于 IO 或网络受限的程序,以及需要提高应用响应性的场景,多线程则更合适。
Python 多进程的优势
不妨把处理器想象成一位企业家。随着企业家的业务增长,需要处理的任务越来越多。如果这位企业家决定独自承担所有任务(如会计、销售、市场、创新等),那么由于一个人在任意时刻只能做有限的事情,她可能会拖累企业的整体效率和表现。比如,在处理创新任务前,她必须先暂停销售任务——这被称为“顺序”执行任务。
大多数企业家都明白,试图一个人包揽一切并不是好主意。因此,他们通常会通过雇佣员工来管理不同部门,以分担不断增长的任务量。这样,任务就可以并行完成——也就是说,不必停止一个任务才能运行另一个任务。雇佣更多员工执行特定任务,就好比使用多个处理器来执行操作。举例来说,计算机视觉项目通常要求处理大量图像数据,这非常耗时;为了加速这一过程,您可以并行处理多张图片。
因此,我们可以说,多进程有助于通过将任务划分并分配给不同处理器,使程序更高效。Python 的 multiprocessing 模块进一步简化了这一点,它作为一个高层工具,通过将任务分配给不同进程来提升程序效率。
Python 的 Multiprocessing 模块基础
我们提到过,Python 进程的生命周期包含三个阶段:新建进程、运行中的进程和已终止的进程。本节将更深入地介绍生命周期的每个阶段,并提供代码示例。您可以访问我们的 DataLab 工作簿跟进学习。
新建进程
新建进程可以理解为通过实例化
Process类而创建的进程。当我们将Process对象赋给一个变量时,就会派生出一个子进程。此时,我们的进程实例还没有做任何事情,因为我们初始化了一个空的 Process 对象。我们可以通过向类的 target 参数传入要在不同进程中运行的函数,来修改 Process 对象的配置以执行特定函数。
如果目标函数还有参数,我们只需将它们作为元组传给 Process 对象的 args 参数即可。
提示:通过交互式课程 Writing Functions in Python 学习如何在 Python 中编写函数。
请注意,我们只是创建了一个新进程,但它尚未运行。
下面看看如何运行新进程。
运行中的进程
运行新进程非常简单:只需调用进程实例的
start()方法。该操作会通过调用 Process 实例的
run()方法来启动进程活动。此外,如果在 Process 实例的 target 参数中指定了自定义函数,run()方法也会负责调用它。回忆一下我们在教程前面提到的:每个进程至少有一个称为主线程的线程——这是默认设置。因此,当一个子进程启动时,会为该子进程创建并启动主线程。主线程负责执行我们在子进程中的所有代码。
在从
start()方法返回到子进程终止这段时间内,我们可以通过对Process实例调用is_alive()方法来检查该进程实例是否存活。注意:如果您在笔记本环境中测试,
is_alive()的调用必须与对start()方法的调用在同一个单元格中,才能捕捉到正在运行的进程。如果进程未在运行,
is_alive()方法的调用将返回 False。已终止的进程
当
run()函数返回或退出时,进程即被终止:通常我们不需要显式执行其他操作。换言之,当您设置为目标函数的指令执行完毕后,可以预期该进程会被终止。因此,is_alive()方法将返回 false。不过,如果进程遇到未处理的异常或抛出了错误,也会被终止。比如,如果您在目标函数中存在错误,进程就会终止。
Process 对象还提供了
terminate()和kill()方法,使用户可以强制终止进程。需要注意的是,强制终止并不是推荐的做法:它可能无法安全关闭所有已打开的资源,或无法保存所需的程序状态。更好的方法是使用受控的关闭方式,例如使用对进程安全的布尔标记或类似工具。
Python 多进程教程
既然您已经理解了多进程的基础知识,我们来做一个示例,演示如何在 Python 中进行并发编程。
我们将创建的函数只会打印一条语句、休眠 1 秒,然后再打印一条语句——想进一步了解函数,请参阅这篇 Python 函数教程。
第一步是创建一个新进程:我们将创建两个。
在笔记本环境中,并发程序看起来如下:
从程序输出可以明显看出,我们的代码某处存在问题。计时信息在第一个进程的中途被打印出来,而且第二条打印语句没有被打印。
这是因为实际上有三个进程在运行:主进程,以及
process_1和process_2。负责统计时间并打印的进程是主进程。若要让主进程在打印时间前先等待,我们必须在运行完两个进程后,对它们调用join()方法。注意:如果您想了解更多,请查看这篇 Stackoverflow 讨论。
来看更新后的代码片段:
问题解决。
本次运行的 Wall time 比第一次稍长。然而,这次运行在返回时间信息之前,已完成对两个目标函数的调用。我们也可以据此思路,让更多进程并发运行。
关于 Python 多进程的最后思考
在本教程中,您学习了如何通过并发运行让 Python 程序更高效。具体而言,您学到了:
想要进一步学习 Python 编程?请查看我们的 Python Programmer 职业路径。无需任何编程基础,路径结束时,您将掌握用 Python 开发软件、整理数据并进行高级分析所需的技能。