Python的多进程和多线程小家电维修

前言:为什么有人说Python的多线程是鸡肋,不是真正意义上的多线程?

1.什么是python的多进程和多线程,有什么区别,哪个更好?

2.为什么要进行上下文切换?什么是上下文切换(进程切换,线程切换)

3.Python多线程为什么耗时更长?GIL是什么?为什么jpython没有这个限制?

4.线程安全是什么?

5.为什么在Python里面推荐使用多进程而不是多线程?

因为每个进程都有各自的一块独立的内存,保证进程彼此间的内存地址空间的隔离。所以,在多道编程中,我们允许多个程序同时加载到内存中(多进程就意味着需要在内存中为进程开辟多个相互独立的地址空间。那么就会占据较大的内存空间),在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

专业点来讲:进程是一个具有一定功能的程序在一个数据集上的一次动态执行过程。进程由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时需要的数据和工作区;程序控制块(PCB)包含程序的描述信息和控制信息,是进程存在的唯一标志

进程是指一个程序在给定数据集合上的一次执行过程,是系统进行资源分配和运行调用的独立单位。可以简单地理解为操作系统中正在执行的程序。也就说,每个应用程序都有一个自己的进程。

每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。

有了进程为什么还要线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

在很早的时候计算机并没有线程这个概念,但是随着时代的发展,只用进程来处理程序出现很多的不足。如当一个进程堵塞时,整个程序会停止在堵塞处,并且如果频繁的切换进程,会浪费系统资源。所以线程出现了

线程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。一个进程可以拥有多个线程,而且属于同一个进程的多个线程间会共享该进行的资源。

由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效地提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。

多进程是由父进程克隆生成子进程,子进程将会拥有和父进程相同的资源,生成的子进程之间以及与父进程都是相互独立的。所以,多进程的创建会消耗系统大量的资源。

因为进程间是相互独立的,所以想要实现进程间的通信,必须使用中间进程

当我们要运行某个程序时,操作系统首先会为其创建至少一个进程(资源分配的基本单元),进程又会为自己创建一个主线程(线程才是指令执行的实体)。当需要多线程的时候,将会由主线程创建出子线程,创建出的子线程将独立执行任务,但将同主线程和其他子线程,共同享用进程资源。

因线程间是在一个进程实体中,同时共享进程资源,所以线程间可以直接交流。

进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。

由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。

因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。

线程与进程的区别可以归纳为以下:

总结,进程和线程还可以类比为火车和车厢:

为什么要进行上下文切换

参考博客更加详细:

上下文的切换:分为进程的上下文切换,线程的上下文的切换,用户态与内核态之间的上下文切换(当用户程序需要调用硬件设备的时候,内核就需要将用户程序切换成系统调用)

进程切换分两步

上下文切换瓶颈判断:

1.如果CPU在满负荷运行,应该符合下列分布,a)UserTime:65%~70%b)SystemTime:30%~35%若该指标过高,基本可以判断为上下文切换过于频繁。c)Idle:0%~5%

2.对于上下文切换要结合CPU使用率来看,如果CPU使用满足上述分布,大量的上下文切换也是可以接受的。

而目前Python的解释器有多种,例如:

CPython:CPython是用C语言实现的Python解释器。作为官方实现,它是最广泛使用的Python解释器。

PyPy:PyPy是用RPython实现的解释器。RPython是Python的子集,具有静态类型。这个解释器的特点是即时编译,支持多重后端(C,CLI,JVM)。PyPy旨在提高性能,同时保持最大兼容性(参考CPython的实现)。

Jython:Jython是一个将Python代码编译成Java字节码的实现,运行在JVM(JavaVirtualMachine)上。另外,它可以像是用Python模块一样,导入并使用任何Java类。

IronPython:IronPython是一个针对.NET框架的Python实现。它可以用Python和.NETframework的库,也能将Python代码暴露给.NET框架中的其他语言。

GIL只在CPython中才有,而在PyPy和Jython中是没有GIL的。

每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。这就导致打印线程执行时长,会发现耗时更长的原因。

并且由于GIL锁存在,Python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,Python的多线程效率并不高的根本原因。

下面看一下官方的说法

定义:

InCPython,theglobalinterpreterlock,orGIL,isamutexthatpreventsmultiple

nativethreadsfromexecutingPythonbytecodesatonce.Thislockisnecessarymainly

becauseCPython’smemorymanagementisnotthread-safe.(However,sincetheGIL

exists,otherfeatureshavegrowntodependontheguaranteesthatitenforces.)

翻译结果:

1、GIL不是Python的特点,而是CPython解释器的特点;

2、在CPython解释器中,GIL是一把互斥锁,用来阻止同一个进程下多个线程的同时执行

3、因为CPython解释器的内存管理并不安全(内存管理—垃圾回收机制)

稍加解释:在没有GIL锁的情况下,有可能多线程在执行一个代码的同时,垃圾回收机制线程对所执行代码的变量直接回收,导致运行报错;

重点:

2、GIL锁是加在CPython解释器上的,是保证解释器级别的数据的安全;

3、GIL锁会导致同一个进程下多个线程的不能同时执行

4、不同的数据除了GIL锁,还需要一把互斥锁,来保证数据处理不会错乱

注意:GIL锁是加在CPython解释器上的,进程先获取GIL锁,在获取CPython解释器

以上,大家大概对GIL锁有了大概的了解,所以接下来有几个问题我们要处理

1.GIL锁有什么好处和坏处?

2.为什么会cpython有GIL锁,jpython没有?

3.为什么cpython要加一把锁?怎么去加锁的?

4.第一个问题GIL锁里面说的线程安全是什么意思?

5.为什么python多线程是伪多线程,还要使用?或者说速度比单线程快?

6.为什么GIL锁还没有删除

简单来说,它在单线程的情况更快,并且在和C库结合时更方便,而且不用考虑线程安全问题,这也是早期Python最常见的应用场景和优势。另外,GIL的设计简化了CPython的实现,使得对象模型,包括关键的内建类型如字典,都是隐含可以并发访问的。锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。

在多线程环境中,Python虚拟机按以下方式执行:

1.设置GIL

2.切换到一个线程去运行

3.运行直至指定数量的字节码指令,或者线程主动让出控制(可以调用sleep(0))

4.把线程设置为睡眠状态

5.解锁GIL

6.再次重复以上所有步骤

另外,从上面的实现机制可以推导出,Python的多线程对IO密集型代码要比CPU密集型代码更加友好。

针对GIL的应对措施:

以下就简单总结一下

好处:

1.保护共享数据:GIL锁可以保护共享数据,防止多个线程同时对共享数据进行修改,从而避免数据不一致的问题。

2.提高性能:GIL锁可以提高性能,因为它可以防止多个线程同时运行,从而减少了线程切换的开销。

3.避免死锁:GIL锁可以避免死锁,因为它可以防止多个线程同时运行,从而避免了线程之间的竞争。

坏处:

1.单个进程下,开启多个线程,无法实现并行,只能实现并发,牺牲执行效率。

2.由于GIL锁的限制,所以多线程不适合计算密集型任务,更适合IO密集型任务

(常见IO密集型任务:网络IO(抓取网页数据)、磁盘操作(读写文件)、键盘输入)

Python是一门解释型的语言,这就意味着代码是解释一行,运行一行,它并不清楚代码全局;

因此,每个线程在调用cpython解释器在运行之前,需要先抢到GIL锁,然后才能运行。

编译型的语言就不会存在GIL锁,编译型的语言会直接编译所有代码,就不会出现这种问题。

Java是个混合型的语言,所以在jpython上就没有GIL锁

额外扩展

编译型语言

定义:在程序运行之前,通过编译器将源程序编译成机器码(可运行的二进制代码),以后执行这个程序时,就不用再进行编译了。

优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高,可以脱离语言环境独立运行。

缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。

总结:执行速度快、效率高;依靠编译器、跨平台性差些。

代表语言:C、C++、Pascal、Object-C以及Swift。

解释型语言

定义:解释型语言的源代码不是直接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,然后执行一句,直至结束。

优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。

缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。

总结:执行速度慢、效率低;依靠解释器、跨平台性好。

代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby。

混合型语言

定义:既然编译型和解释型各有缺点就会有人想到把两种类型整合起来,取其精华去其糟粕,就出现了半编译,半解释型语言。

比如C#,C#在编译的时候不是直接编译成机器码而是中间码,.NET平台提供了中间语言运行库运行中间码,中间语言运行库类似于Java虚拟机。.NET在编译成IL代码后,保存在dll中,首次运行时由JIT在编译成机器码缓存在内存中,下次直接执行。严格来说混合型语言属于解释型语言,C#更接近编译型语言。

Java即是编译型的,也是解释型语言,总的来说Java更接近解释型语言。

可以说它是编译型的。因为所有的Java代码都是要编译的,.java不经过编译就什么用都没有。同时围绕JVM的效率问题,会涉及一些如JIT、AOT等优化技术,例如JIT技术,会将热点代码编译成机器码。而AOT技术,是在运行前,通过工具直接将字节码转换为机器码。

可以说它是解释型的。因为Java代码编译后不能直接运行,它是解释运行在JVM上的,所以它是解释运行的。

这里就需要使用到老师课堂上讲解的案例了,其实以下的笔记总结一句话,分为2部分,并发和并行是2回事,python的并发并没有问题,到了c解析器那边的时候,因为GIL的原因,就不予许并行了。

(重点:Python是可以并发的,C解析器因为GIL原因不予许并行,跟python毫无关系)

本篇章就用课堂上通俗易懂的语句来讲解一下。这里就直接把笔记贴出来吧。

这里用老师课上的一张图来讲解一下。

1.为什么每次finalnum:0的值都不是一样的?

这里首先讲解一下python自己的解释器环境。

比如num(图片上为A,老师标错了)=10。

假设有这种奇葩的情况

所以说为什么很奇葩,这是一个概率问题,如果运气好,全部调用就返回,按照顺序快速执行,那么结果肯定正确。但是肯定不能保证都是上一个函数执行好并返回数据,下一个接着执行。更多的还是如同上面讲解的情况,很多次函数的值被覆盖,所以就出现了num的值不正常的

2.不是说python受GIL锁的影响,不会出现这种非线程安全的问题吗?

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTELC++,VisualC++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

简单来说,python调用c的原生线程去执行,那么是c的特性上了锁,python完全不依赖,只是默认我们用的都是cpython,所以受了c的锁的影响.既然有锁,为什么num的结果不同。

这里请出上面的图。

当然,这样加了锁,串行就变成并行了,就没有多线程了,如果要执行任务,不需要确定结果,就可以使用多线程不加锁。如果要计算结果,大家还是慎重

补充:以下是老师博客的补充,但是本人觉得,视频已经讲解的很清楚了,这里还是贴出来一下。

线程安全指的是内存的安全,在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。

所以线程安全指的是,在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程“破坏”。

这个问题前面4个问题提了很多次,python说是伪多线程的原因是因为默认大家使用的都是cpython,而c解释器有GIL锁。所以可以说他是伪多线程。

但是还是那句话,并发和并行是2回事,python的并发多线程受到了cpython的影响,导致大家认为他是伪多线程,可以这么说但是不绝对,因为python的并发是可以执行的,但是到了c解析器那边,只允许并行一个任务,拖了python的后腿,就比如jpython就是没有这个GIL锁的。

至于第二个问题,为什么还要使用?因为python多并发是可以执行的,只是到了C解析器的时候被Gil拦住了,只予许执行一个线程

2.1里面也带过一笔,有一些历史原因在里面在Cpython虚拟机(解释器),难以移除GIL,同时删除GIL会使得Python3在处理单线程任务方面比Python2慢,可以想像会产生什么结果。你不能否认GIL带来的单线程性能优势,这也就是为什么Python3中仍然还有GIL。

小提示(GIL锁的历史原因):

Python为了利用多核CPU,开始支持多线程。而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁,于是有了GIL这把超级大锁。因为有了GIL,所以我们的Python可以实现多进程,但是这是一个假的多进程,虽然它会利用多个CPU共同协作,但实则是利用一个CPU的资源。

但是这种GIL导致我们的多进程并不是真正的多进程,所以它的效率很低。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。如果推到重来,多线程的问题依然还是要面对,但是至少会比目前GIL这种方式会更优雅。所以简单的说:GIL的存在更多的是历史原因。

Python提供两个模块进行多线程的操作,分别是thread和threading,前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。

importthreading#这个函数名可随便定义defrun(n):print("currenttask:",n)if__name__=="__main__":t1=threading.Thread(target=run,args=("thread1",))t2=threading.Thread(target=run,args=("thread2",))t1.start()t2.start()

importthreadingclassMyThread(threading.Thread):def__init__(self,n):super(MyThread,self).__init__()#重构run函数必须要写self.n=ndefrun(self):print("currenttask:",n)if__name__=="__main__":t1=MyThread("thread1")t2=MyThread("thread2")t1.start()t2.start()

join函数执行顺序是逐个执行每个线程,执行完毕后继续往下执行。主线程结束后,子线程还在运行,join函数使得主线程等到子线程结束时才退出。不然一般情况下,主线程执行完成,程序退出了,都没有管子进程有没有执行完成

importthreadingdefcount(n):whilen>0:n-=1if__name__=="__main__":t1=threading.Thread(target=count,args=("100000",))t2=threading.Thread(target=count,args=("100000",))t1.start()t2.start()#将t1和t2加入到主线程中t1.join()t2.join()

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。如下:

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。

考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。

那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。

经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

用法的基本步骤:

importthreadingimporttimenum=0mutex=threading.Lock()classMyThread(threading.Thread):defrun(self):globalnumtime.sleep(1)ifmutex.acquire(1):num=num+1msg=self.name+':numvalueis'+str(num)print(msg)mutex.release()if__name__=='__main__':foriinrange(5):t=MyThread()t.start()案例代码2:课程案例,可以参考更加详细的课程笔记

为了满足在同一线程中多次请求同一资源的需求,Python提供了可重入锁(RLock)。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。具体用法如下:

简单来说,就是一个大锁中还要再包含子锁

importthreading,timedefrun1():print("grabthefirstpartdata")lock.acquire()globalnumnum+=1lock.release()returnnumdefrun2():print("grabthesecondpartdata")lock.acquire()globalnum2num2+=1lock.release()returnnum2defrun3():lock.acquire()res=run1()print('--------betweenrun1andrun2-----')res2=run2()lock.release()print(res,res2)if__name__=='__main__':num,num2=0,0'''如果不加入Rock,那么这么多锁,锁就混乱了,不知道谁是谁的锁,输出的结果一定是一个死循环rlock可以理解成为一个锁关系的记录表,让每一层知道谁是谁的锁。'''lock=threading.RLock()foriinrange(10):t=threading.Thread(target=run3)t.start()whilethreading.active_count()!=1:print(threading.active_count())else:print('----allthreadsdone---')print(num,num2)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

本章主要内容是让线程产生因果关系,比如车因为红绿灯而走或者停,怎么实现呢?

通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。

上面的内容很简单,比较绕的地方在于:event.set()和event.wait,这里要反着想,event.set()有了这个,就是指示可以通行,意思如果没有设置event.set(),那么就是默认event.wait(),所有车都得等着,记住这一条在去看代码,就会清晰很多。

如果希望主线程执行完毕之后,不管子线程是否执行完毕都随着主线程一起结束。我们可以使用setDaemon(bool)函数,它跟join函数是相反的。它的作用是设置子线程是否随主线程一起结束,必须在start()之前调用,默认为False。

如果需要规定函数在多少秒后执行某个操作,需要用到Timer类。具体用法如下:

fromthreadingimportTimerdefshow():print("Pyhton")#指定一秒钟之后执行show函数t=Timer(1,hello)t.start()

一般都是第三方模块,用的比较少,需要的时候在去查吧。

Python要进行多进程操作,需要用到muiltprocessing库,其中的Process类跟threading模块的Thread类很相似。所以直接看代码熟悉多进程。包括什么信号量,多进程里面也有,用法和多线程类似,这里就不做过多解释了

frommultiprocessingimportProcessdefshow(name):print("Processnameis"+name)if__name__=="__main__":proc=Process(target=show,args=('subprocess',))proc.start()proc.join()

frommultiprocessingimportProcessimporttimeclassMyProcess(Process):def__init__(self,name):super(MyProcess,self).__init__()self.name=namedefrun(self):print('processname:'+str(self.name))time.sleep(1)if__name__=='__main__':foriinrange(3):p=MyProcess(i)p.start()foriinrange(3):p.join()

进程之间不共享数据的。如果进程之间需要进行通信,则要用到Queue模块或者Pipe模块来实现。

Queue是多进程安全的队列,可以实现多进程之间的数据传递。它主要有两个函数put和get。

Queue模块中的常用方法:

具体用法如下:

frommultiprocessingimportProcess,Queuedefput(queue):queue.put('Queue用法')if__name__=='__main__':queue=Queue()pro=Process(target=put,args=(queue,))pro.start()print(queue.get())pro.join()

frommultiprocessingimportProcess,Pipedefshow(conn):conn.send('Pipe用法')conn.close()if__name__=='__main__':parent_conn,child_conn=Pipe()pro=Process(target=show,args=(child_conn,))pro.start()print(parent_conn.recv())pro.join()

上面也说了,这里更适合叫做进程间的数据传递,但是也有可以类似进程间数据共享的方式,那什么叫做进程间数据共享?那么就是说2个进程都可以修改这个数据,比如一个字典,2个进程都可以同时往里面放数据,见以下代码。

通过manages实现

manager()返回的管理器对象控制一个服务器进程,该进程持有Python对象,并允许其他进程使用代理操作它们。

由manager()返回的管理器将支持列表、字典、命名空间、锁、RLock、信号量、BoundedSemaphore、条件、事件、Barrier、队列、值和数组类型。例如,

(这章还是有点晦涩难懂,还是需要多看)

创建多个进程,我们不用傻傻地一个个去创建。我们可以使用Pool模块来搞定。Pool常用的方法如下:

具体用法见示例代码:

#coding:utf-8importmultiprocessingimporttimedeffunc(msg):print("msg:",msg)time.sleep(3)print("end")if__name__=="__main__":#维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去pool=multiprocessing.Pool(processes=3)foriinrange(5):msg="hello%d"%(i)#非阻塞式,子进程不影响主进程的执行,会直接运行到pool.join()pool.apply_async(func,(msg,))#阻塞式,先执行完子进程,再执行主进程#pool.apply(func,(msg,))print("Mark~Mark~Mark~~~~~~~~~~~~~~~~~~~~~~")#调用join之前,先调用close函数,否则会出错。pool.close()#执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束pool.join()print("Sub-process(es)done.")如上,进程池Pool被创建出来后,即使实际需要创建的进程数远远大于进程池的最大上限,p.apply_async(test)代码依旧会不停的执行,并不会停下等待;相当于向进程池提交了10个请求,会被放到一个队列中;

当执行完p1=Pool(5)这条代码后,5条进程已经被创建出来了,只是还没有为他们各自分配任务,也就是说,无论有多少任务,实际的进程数只有5条,计算机每次最多5条进程并行。

当Pool中有进程任务执行完毕后,这条进程资源会被释放,pool会按先进先出的原则取出一个新的请求给空闲的进程继续执行;

当Pool所有的进程任务完成后,会产生5个僵尸进程,如果主线程不结束,系统不会自动回收资源,需要调用join函数去回收。

join函数是主进程等待子进程结束回收系统资源的,如果没有join,主程序退出后不管子进程有没有结束都会被强制杀死;

创建Pool池时,如果不指定进程最大数量,默认创建的进程数为系统的内核数量.

在这个问题上,首先要看下你的程序是属于哪种类型的。一般分为两种:CPU密集型和I/O密集型。

如果程序是属于CPU密集型,建议使用多进程。而多线程就更适合应用于I/O密集型程序。

简单来说就是这样,但是只能说:没有最好,只有更好。根据实际情况来判断,哪个更加合适就是哪个好。

我们按照多个不同的维度,来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。

看起来比较简单,优势对比上是“线程3.5v2.5进程”,我们只管选线程就是了?

呵呵,有这么简单我就不用在这里浪费口舌了,还是那句话,没有绝对的好与坏,只有哪个更加合适的问题。我们来看实际应用中究竟如何判断更加合适。

1)需要频繁创建销毁的优先用线程

原因请看上面的对比。

这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

2)需要进行大量计算的优先使用线程

所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

这种原则最常见的是图像处理、算法处理。

当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

4)可能要扩展到多机分布的用进程,多核分布的用线程

原因请看上面对比。

5)都满足需求的情况下,用你最熟悉、最拿手的方式

至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。

需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

描述PythonGIL的概念,以及它对Python多线程的影响?

编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。

参考答案:

GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。

Python语言和GIL没有任何关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。

线程释放GIL锁的情况:在IO操作等可能会引起阻塞的systemcall之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL

Python使用多进程是可以利用多核的CPU资源的。

多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁。

THE END
1.芳菲聆听:正月十六(芳菲网络信息小港湾)小家电维修 屈家岭: 13451211643小张 13477575511赵 开锁换锁 五三屈家岭: 13297622949万开锁城万开锁城 江夏区大花岭: 开锁133 8752 8086? 一:初见 · 花艺(屈家岭) ?初见.花艺 ? 15337474350 00:12 鲜花永远是平淡生活里温柔的光! 一束花的浪漫永不过时?ˋˊ? https://www.meipian.cn/516ml0hp
2.董明珠自传《行棋无悔》摘录与评注这是董明珠的第二本传记,出版于这是董明珠的第二本传记,出版于2006年。相较于第一本《棋行天下》主要讲述董明珠个人的成长奋斗经历,这一本更多地展现了董明珠对空调行业的思考、管理理念与营销心得,对不关心或不了解空调行业的读者或许有点枯燥。书中开头的“淮地哗变”像电视剧一般精彩,展现了董明珠https://xueqiu.com/4373567778/155512197
3.家电销售经验范文6篇(全文)但是我认为销售能力的提升可通过两个方面,一是产品知识积累、二是销售技巧提高、技巧是需要实战的,但掌握产品知识应该是我的强项,可作为突破口加强学习)、业务能力、管理能力(我对管理的认识是通过分析、计划、实施、控制这几个动作来达成预期目标),是目前我对市场操作的理解,特献给所有的营销同行,希望对营销新人有https://www.99xueshu.com/w/fileh2nd69mn.html
4.办公区域空调使用管理规定(精选15篇)中央空调的运行管理是指在中央空调运行规程中, 运行组人员对系统中的规定部位进行巡视, 做好巡视纪录, 并明确巡视的内容以及出现异常事故后的处理规定, 使运行组值班人员依照规程运行管理, 保证设备运行正常, 少出故障。 4) 中央空调的维修养护管理 中央空调维修养护管理是为了避免设备在运行过程中出现重大故障, 以便https://www.360wenmi.com/f/file4do11m0v.html
5.新资讯:北野望在线观看中文字幕北野望实况网鼠标点一下变两下可以维修吗_鼠标点一下变两下 2023-05-31 micro usb接口四根线图解_micro USB-世界独家 2023-05-31 热门看点:张之臻晋级法网第二轮 2023-05-31 山东电子职业学院官网(山东电子职业学院) 2023-05-31 神舟十六号成功发射 美媒点赞:中国航天计划再迈新台阶-简讯 2023-05-31 全球热议:http://m.cqtimes.cn/news/yaowen/20230606/232348.html
6.电子产品市场调查报告(通用5篇)目前的家电售后维修服务方式主要有两种:一是厂家委托商家的维修站对顾客提供该产品的售后服务,一些中小品牌企业大多采用这种方式;二是厂家投资建立售后服务站,直接向顾客提供售后服务。大品牌企业为了降低管理成本,也多委托特约维修站。 2、家电维修服务业存在的问题 https://www.unjs.com/z/1803862.html
7.海尔家电为该公司生产销售的电冰箱提供上门安装售后维修等服务D. 数据库管理系统 查看完整题目与答案 下列说法有误的一项是( )。 A. 现代的市场经济是政府干预和市场经济相结合的混合经济 B. 现代市场经济的发展在很大程度上取决于“划分市场与政府的合理界限是一个持久的问题”的有效解决 C. 市场经济和社会主义基本制度的结合是不可行的 D. 社会主义市场经济https://www.shuashuati.com/ti/2c281d666a1d4a308703c8238c76c7d3.html?fm=bdbdsa6b01543c6fdadeeb77a121c036181fe
8.洛龙家电洛龙家电专注冰箱、电视、洗衣机、壁挂炉、空气能、太阳能、热水器、集成灶、燃气灶维修及洛龙中央空调修理/移机加氟/安装清洗,附近师傅上门维修电话:18109031094或联系河南售后维修点。http://luanchuan.ttcaiwu.cn/
9.[转帖]我和我初恋女友的母亲上了床(催人泪下的文章)电源设计与维修技术 (设计,开发,维修) 我的维修生涯 二手淘金 - 跳蚤市场 我的维修经验 维修人才-招聘求职 家电维修视频教学 原创摄影交流 文学版-休闲文学 家电维修 电视维修 OLED 彩电维修(CRT) 液晶电视维修 制冷维修 麻将机_麻将机维修 微波炉维修 电磁炉维修 - 光波炉维修 投影仪投影机维修 - 变频大屏幕电https://www.chinadz.com/news__340623