您当前的位置:首页 > 互联网教程

python多进程和多线程的区别

发布时间:2025-05-22 00:54:34    发布人:远客网络

python多进程和多线程的区别

一、python多进程和多线程的区别

1、进程是程序(软件,应用)的一个执行实例,每个运行中的程序,可以同时创建多个进程,但至少要有一个。每个进程都提供执行程序所需的所有资源,都有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间)。进程可以包含线程,并且每个进程必须有至少一个线程。每个进程启动时都会最先产生一个线程,即主线程,然后主线程会再创建其他的子线程。

2、线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不独立拥有系统资源,但它可与同属一个进程的其它线程共享该进程所拥有的全部资源。每一个应用程序都至少有一个进程和一个线程。在单个程序中同时运行多个线程完成不同的被划分成一块一块的工作,称为多线程。

3、举个例子,某公司要生产一种产品,于是在生产基地建设了很多厂房,每个厂房内又有多条流水生产线。所有厂房配合将整个产品生产出来,单个厂房内的流水线负责生产所属厂房的产品部件,每个厂房都拥有自己的材料库,厂房内的生产线共享这些材料。公司要实现生产必须拥有至少一个厂房一条生产线。换成计算机的概念,那么这家公司就是应用程序,厂房就是应用程序的进程,生产线就是某个进程的一个线程。

4、线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。假设你正在读一本书,没有读完,你想休息一下,但是你想在回来时继续先前的进度。有一个方法就是记下页数、行数与字数这三个数值,这些数值就是execution context。如果你的室友在你休息的时候,使用相同的方法读这本书。你和她只需要这三个数字记下来就可以在交替的时间共同阅读这本书了。

5、线程的工作方式与此类似。CPU会给你一个在同一时间能够做多个运算的幻觉,实际上它在每个运算上只花了极少的时间,本质上CPU同一时刻只能干一件事,所谓的多线程和并发处理只是假象。CPU能这样做是因为它有每个任务的execution context,就像你能够和你朋友共享同一本书一样。

6、同一个进程中的线程共享同一内存空间,但进程之间的内存空间是独立的。

7、同一个进程中的所有线程的数据是共享的,但进程之间的数据是独立的。

8、对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。

9、线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。

10、同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。

11、创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。

12、一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。

13、线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)。

14、由于现代cpu已经进入多核时代,并且主频也相对以往大幅提升,多线程和多进程编程已经成为主流。Python全面支持多线程和多进程编程,同时还支持协程。

二、python多进程为什么一定要

前面讲了为什么Python里推荐用多进程而不是多线程,但是多进程也有其自己的限制:相比线程更加笨重、切换耗时更长,并且在python的多进程下,进程数量不推荐超过CPU核心数(一个进程只有一个GIL,所以一个进程只能跑满一个CPU),因为一个进程占用一个CPU时能充分利用机器的性能,但是进程多了就会出现频繁的进程切换,反而得不偿失。

不过特殊情况(特指IO密集型任务)下,多线程是比多进程好用的。

举个例子:给你200W条url,需要你把每个url对应的页面抓取保存起来,这种时候,单单使用多进程,效果肯定是很差的。为什么呢看

例如每次请求的等待时间是2秒,那么如下(忽略cpu计算时间):

1、单进程+单线程:需要2秒*200W=400W秒==1111.11个小时==46.3天,这个速度明显是不能接受的2、单进程+多线程:例如我们在这个进程中开了10个多线程,比1中能够提升10倍速度,也就是大约4.63天能够完成200W条抓取,请注意,这里的实际执行是:线程1遇见了阻塞,CPU切换到线程2去执行,遇见阻塞又切换到线程3等等,10个线程都阻塞后,这个进程就阻塞了,而直到某个线程阻塞完成后,这个进程才能继续执行,所以速度上提升大约能到10倍(这里忽略了线程切换带来的开销,实际上的提升应该是不能达到10倍的),但是需要考虑的是线程的切换也是有开销的,所以不能无限的启动多线程(开200W个线程肯定是不靠谱的)3、多进程+多线程:这里就厉害了,一般来说也有很多人用这个方法,多进程下,每个进程都能占一个cpu,而多线程从一定程度上绕过了阻塞的等待,所以比单进程下的多线程又更好使了,例如我们开10个进程,每个进程里开20W个线程,执行的速度理论上是比单进程开200W个线程快10倍以上的(为什么是10倍以上而不是10倍,主要是cpu切换200W个线程的消耗肯定比切换20W个进程大得多,考虑到这部分开销,所以是10倍以上)。

还有更好的方法吗看答案是肯定的,它就是:

4、协程,使用它之前我们先讲讲what/why/how(它是什么/为什么用它/怎么使用它)what:

协程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。

目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。

不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。

而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。

因为协程是用户自己来编写调度逻辑的,对CPU来说,协程其实是单线程,所以CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程。

python里面怎么使用协程看答案是使用gevent,使用方法:看这里使用协程,可以不受线程开销的限制,我尝试过一次把20W条url放在单进程的协程里执行,完全没问题。

所以最推荐的方法,是多进程+协程(可以看作是每个进程里都是单线程,而这个单线程是协程化的)多进程+协程下,避开了CPU切换的开销,又能把多个CPU充分利用起来,这种方式对于数据量较大的爬虫还有文件读写之类的效率提升是巨大的。

from multiprocessing import Process

from gevent import monkey; monkey.patch_all()import sys

sys.setdefaultencoding('utf8')

r= s.get(url,timeout=1)#在这里抓取页面

gevent.joinall(tasks)#使用协程来执行

def task_start(filepath,flag= 100000):#每10W条url启动一个进程with open(filepath,'r') as reader:#从给定的文件中读取urlurl= reader.readline().strip()

task_list= []#这个list用于存放协程任务

i= 0#计数器,记录添加了多少个url到协程队列while url!='':

task_list.append(gevent.spawn(fetch,url,queue))#每次读取出url,将任务添加到协程队列if i== flag:#一定数量的url就启动一个进程并执行p= Process(target=process_start,args=(task_list,))p.start()

url= reader.readline().strip()

if task_list not []:#若退出循环后任务队列里还有url剩余p= Process(target=process_start,args=(task_list,))#把剩余的url全都放到最后这个进程来执行p.start()

if __name__=='__main__':

task_start('./testData.txt')#读取指定文件细心的同学会发现:上面的例子中隐藏了一个问题:进程的数量会随着url数量的增加而不断增加,我们在这里不使用进程池multiprocessing.Pool来控制进程数量的原因是multiprocessing.Pool和gevent有冲突不能同时使用,但是有兴趣的同学可以研究一下gevent.pool这个协程池。

另外还有一个问题:每个进程处理的url是累积的而不是独立的,例如第一个进程会处理10W个,第二个进程会变成20W个,以此类推。最后定位到问题是gevent.joinall()导致的问题,有兴趣的同学可以研究一下为什么会这样。不过这个问题的处理方案是:主进程只负责读取url然后写入到list中,在创建子进程的时候直接把list传给子进程,由子进程自己去构建协程。这样就不会出现累加的问题

三、Python多进程并行编程实践-mpi4py的使用

1、在高性能计算项目中,尽管编译型语言如C、C++、Fortran因其效率受到青睐,Python的灵活性与易用性使其在算法开发与验证中大放异彩,也因此在高性能计算领域中占有一席之地。本文将简要介绍如何在Python环境下利用MPI接口进行集群上的多进程并行计算。

2、消息传递接口MPI(Message Passing Interface)是一种并行计算模型,其基本思想是通过进程间通信实现任务分配与协调,以完成同一个任务。多个进程在同一个通信域中运行,每个进程都有唯一的编号,通过MPI提供的接口,程序员可为不同编号的进程分配任务并实现进程间的交流。

3、在Python中实现并行计算,由于CPython解释器存在全局解释器锁(GIL),多线程并行计算受限。因此,Python多进程并行编程成为实现并行计算的优选方式。Python提供了多种创建进程的手段,如`os.fork()`和`multiprocessing`模块。在先前的文章《Python多进程并行编程实践-multiprocessing模块》中,我们已探讨了使用进程池管理Python进程,以及通过`multiprocessing`模块的`Manager`实现分布式进程计算的方法。

4、在Python中,进程间通信主要依赖于`multiprocessing`模块提供的工具如`pipe`、`queue`、`Array`和`Value`。尽管这些工具使得进程间通信变得较为直接,但在实际应用中,灵活性和效率仍然有限。而MPI正是在这一方面表现得更为优秀,因此,能够通过调用MPI接口在Python中实现并行计算,无疑将带来更高效的性能和更强大的并行处理能力。

5、mpi4py是一个构建在MPI之上,使用Cython编写的Python库。它极大地方便了Python数据结构在多进程间的传递,实现了MPI标准接口的丰富功能,包括点对点通信、组内集合通信、非阻塞通信、重复非阻塞通信、组间通信等。mpi4py对Python对象、numpy数组以及Fortran/C/C++程序都有很好的支持,通过封装,使得用户能够使用Python代码实现与MPI库的高效交互。

6、使用mpi4py在Python环境中进行并行编程,首先需要初始化MPI环境,通常在导入`mpi4py`时,环境已经自动初始化。mpi4py通过`Init()`和`Finalize()`接口管理MPI环境的初始化与结束。同时,它还支持自动调用`MPI_Finalize()`以结束环境,确保资源的释放。

7、在并行计算中,进程间通信是关键。mpi4py提供了丰富的接口来支持点对点通信和组内通信。点对点通信允许进程之间传递Python内置对象(基于pickle序列化)和numpy数组,同时支持直接传递数据对象,以实现高效的通信。组通信则允许在进程组内进行数据的广播、发散、收集等操作,简化了编程并提高了可移植性。

8、在实际应用中,使用mpi4py实现并行计算,可以显著提升计算效率。例如,通过将计算任务分散到多个进程上执行,可以充分利用多核资源,加速计算过程。在本文中,通过使用mpi4py将二重循环绘制任务并行化,将原计算任务在单进程中运行与在10个进程中并行运行进行了对比,结果显示并行化后的效率提升了约10倍。

9、总之,mpi4py为Python环境下的并行计算提供了一个强大且易用的工具集,使得开发者能够轻松地在Python中实现复杂并行任务的高效处理。通过了解和掌握mpi4py的使用,可以极大地提升计算效率,为高性能计算项目提供有力支持。

10、 mpi4py官方文档:提供详细的接口文档与示例代码,帮助开发者深入了解并高效利用mpi4py。

11、 MPI官方文档与教材:了解MPI的基本原理与实现方式,为深入理解mpi4py接口提供理论基础。