[Python基础] 多进程还是多线程

Posted by Chase Shen on 2022-02-10
Estimated Reading Time 5 Minutes
Words 1.5k In Total
Viewed Times

选择多进程还是多线程取决于你的具体需求和情况。下面是一些考虑因素:

1. 并发性能需求(多进程)
如果你的应用程序需要处理大量的并发任务,并且这些任务是计算密集型的(CPU 密集型),那么多进程可能更适合,因为每个进程有自己独立的内存空间和 CPU 执行上下文,可以充分利用多核 CPU。
使用多进程进行计算的主要原因是Python中的全局解释器锁(Global Interpreter Lock,GIL)限制了多线程并发执行的效率。GIL是Python解释器中的一个机制,它会确保在解释器级别同一时刻只有一个线程在执行Python字节码,这会导致多线程程序在CPU密集型任务中无法充分利用多核处理器的优势。
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如对视频进行编码解码或者格式转换等等,这种任务全靠CPU的运算能力,虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低。计算密集型任务由于主要消耗CPU资源,这类任务用Python这样的脚本语言去执行效率通常很低,最能胜任这类任务的是C语言。

2. I/O 密集型任务(多线程)
如果你的应用程序主要是进行 I/O 操作,比如网络请求、文件读写等,那么多线程可能更适合,因为线程之间共享同一进程的内存空间,如果启动多任务,就可以减少I/O等待时间从而让CPU高效率的运转,可以更高效地进行并发的 I/O 操作。

3. 内存消耗(多线程)
多进程会有一定的额外开销,因为每个进程都有自己独立的内存空间,而多线程共享同一进程的内存空间。如果你的应用程序需要大量的并发任务,并且每个任务都需要占用大量的内存,那么多线程可能更适合,因为它们共享同一进程的内存空间,开销较小。

4. 操作系统限制(多线程)
操作系统在切换进程或者线程时,需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。所以,多任务一旦多到一个限度,反而会使得系统性能急剧下降,最终导致所有任务都做不好。
在某些操作系统中,进程的创建和切换可能会比线程更昂贵,因此在这些系统中可能更倾向于使用线程。

综上所述,如果你的应用程序需要处理大量的计算密集型任务,并且需要充分利用多核 CPU,那么多进程可能更适合。如果你的应用程序主要是进行 I/O 操作,并且需要更高的并发性能,那么多线程可能更适合。同时,你也可以考虑结合使用多进程和多线程来充分利用系统资源,以满足不同任务的需求。

单线程+异步I/O

现代操作系统对I/O操作的改进中最为重要的就是支持异步I/O。如果充分利用操作系统提供的异步I/O支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。Nginx就是支持异步I/O的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式,这也是当下并发编程的一种流行方案。

在Python语言中,单线程+异步I/O的编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

当涉及异步I/O时,Python中常用的模块是 asyncio ,通过 asyncio 可以实现单线程下的异步I/O编程。

1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio

async def download_task():
print("开始下载...")
await asyncio.sleep(2) # 模拟下载耗时
print("下载完成!")

async def main():
task = asyncio.create_task(download_task())
await task

if __name__ == "__main__":
asyncio.run(main())

如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !