搞懂? Python 多线程 多进程(先吃饭再喝汤?仍是吃饭喝汤同时进行?)

若是只能用一只手吃饭+喝汤,吃饭耗时十五分钟,喝汤五分钟,这样确定耗时。人家还想早点吃完开一把LOL呢,那么很简单这时候,咱们就会想到左手喝汤,右手吃饭这样同时进行。这样时间上吃饭就获得了优化。python

多线程这个话题天然离不开进程线程这两个keyword数组

1.进程

首先简单介绍下进程:计算机程序只是存储在磁盘上的可执行二进制(或者其余类型)文件。从硬盘中读取他们到内存中这样才拥有了生命周期。进程或者说重量级进程(同义)是一个执行中的程序,它拥有本身的地址空间、内存、数据栈以及其余用于跟踪执行的辅助数据。咱们的操做系统(Operating System,简称OS)管理全部进程的执行,并为这些进程分配合理的地址空间。进程也能够经过fork或者spawn新的进程来执行其余任务,新的进程固然也拥有本身的内存和数据栈,因此只能采用进程间通讯(IPC)的方式。网络

2.线程

线程或轻量级进程(同义)与进程相似,不过它们是在统一进程下执行的,而且共享相同的上下文,能够理解一个进程池中的东西线程共享。数据结构

线程包括三部分:开始--执行--结束,指令指针用于记录当前运行的上下文。当其余线程运行时,它能够被抢占(中断)和临时挂起(sleep),这种作法叫作让步(yielding)。多线程

固然线程中也会有主线程,因此说一个进程中的各个线程与主线程共享一片数据空间。若是一顿午餐至关于一个进程的话,你能够吃本身的午餐却很差意思吃别人的午餐(由于吃别人的午餐须要跟他进行沟通(IPC通讯),沟通很差还会被锤!!!),吃本身的想怎么吃就怎么吃。线程通常是并发执行的,因为这种并发和数据共享机制,使得多任务间协做成为可能。并发

在单核的CPU中真正的并发是不可能的,在一段时间内你只可能夹一道菜,吃两口再夹别的菜,以后也能够再回头吃这个菜。 这样的一个为所欲为的夹菜的顺序就成了无言的一个排列。线程也是同样,一个线程运行一段时间而后开始休眠,让步给其余的线程(再次排队等待更多的CPU时间)。在整个进程执行过程当中,每一个线程执行它本身特定的任务,在必要的时和其余线程进行结果通讯。函数

这样不免会联想出问题,若是共享数据,那么会有风险。oop

Q1:

若是两个或多个线程访问同一片数据,因为数据访问顺序不一样,可能致使结果不一致。这种状况就是“竞态条件”。不过大多数线程库都有一些同步原语,以容许线程管理器控制和访问。学习

Q2 :

固然啦 吃饭总会有本身爱吃的菜,因此每道菜的摄入量不会公平。也就是说线程没法给予公平的执行时间。若是没有专门的多线程状况进行修改,会致使CPU的时间分配向贪婪函数倾斜。优化

接下来要谈论的就是正题:

python中线程

1.GIL(全局解释锁):python代码的执行是由Python虚拟机(解释器主循环)进行控制的。在解释器主循环中只能由一个控制线程在执行,就像单核CPU系统的多进程一个道理,内存中能够有不少程序,可是在任意给定的时刻只能有一个程序在运行。尽管python解释器能够运行多个线程,可是在任意给定时刻只有一个线程会被解释器执行。

对python虚拟机的访问是由全局解释锁GIL控制的,这个锁就是用来保证同时只能由一个线程运行。

在python虚拟机中将按照下面的方式来运行。

在Python多线程下,每一个线程的执行方式:

一、获取GIL

二、执行代码直到sleep或者是python虚拟机将其挂起。

**三、释放GIL **

可见,某个线程想要执行,必须先拿到GIL,咱们能够把GIL看做是“通行证”,而且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不容许进入CPU执行。

在Python2.x里,GIL的释放逻辑是当前线程碰见IO操做或者ticks计数达到100(ticks能够看做是Python自身的一个计数器,专门作用于GIL,每次释放后归零,这个计数能够经过 sys.setcheckinterval 来调整),进行释放。

而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。而且因为GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为何在多核CPU上,python的多线程效率并不高。

那么是否是python的多线程就彻底没用了呢?
在这里咱们进行分类讨论:
一、CPU密集型代码(各类循环处理、计数等等),在这种状况下,因为计算工做多,ticks计数很快就会达到阈值,而后触发GIL的释放与再竞争(多个线程来回切换固然是须要消耗资源的),因此python下的多线程对CPU密集型代码并不友好。

二、IO密集型代码(文件处理、网络爬虫等),多线程可以有效提高效率(单线程下有IO操做会进行IO等待,形成没必要要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,能够不浪费CPU的资源,从而能提高程序执行效率)。因此python的多线程对IO密集型代码比较友好。

2.退出线程:

当一个线程完成函数执行的时候,它就会退出。还能够经过thread.exit()之类的退出函数,或者sys.exit()之类的退出python进程的标准方法,或者抛出SystemExit异常,来使线程退出。可是你不能直接终止一个线程

主线程:主线程应该是一个好的管理者去管理好调度好好的吃这一顿饭(收集每个线程的结果,生成一个有意义的最终结果。)

3.python中使用线程:

通常来讲如今咱们平常使用的电脑都是64位的因此咱们安装的python解释器默认都是支持现成的,只须要import好模块便可使用。

thread:这个模块不介绍也不推荐使用,缘由是当thread中主线程结束时,全部的其余线程也都强制结束,不会发出警告或者适当的清理。

threading:经常使用此模块,由于至少threading模块能够确保重要的子线程在进程退出前结束,也成守护线程。

看一个简单的例子

下面的例子是个basic的例子,没有使用线程。吃饭使用5s时间喝汤使用2s时间,是串行的,参数loop是循环次数。

结果:

从结果来看喝汤耗时4s,吃饭耗时10s,总共耗时14秒。

加入threading module

结果:

解析:

建立线程数组,用于线程载入,调用threading module的Thread()方法建立线程,而后调用。

结果显示吃饭喝汤同时进行。start()开始线程活动,join()等待线程终止。若是不使用join()方法对每一个线程作等待终止,那么在线程运行过程当中会打印吃完XXX

变强的过程在于思考优化,因此咱们能够优化线程的建立(线程建立代码重复冗余度很高)

那么咱们能够集中遍历建立,而后基于此目的微调下主函数,结果以下:

结果:

有点瑕疵应该在print的部分改成i+1,循环次数也强制写死为2次,固然这只是个demo,若是考虑再加个列表读入times 参数,此时能够添加判断是否大于零,能够本身试一试再也不赘述。

在实际开发中,咱们这种利用线程的方式确定不是咱们经常使用的方式,由于本身在用的时候会传入不少参数,因此咱们就必须用到继承Threading,来建立咱们自用的线程类

结果同以前的就不在展现。

慢慢的你会发现学习一部分慢慢的修改,你的代码也会愈来愈合理,思考的过程就是这样。

因为目前咱们的CPU 都是多核的,因此说每个进程中均可以有一个线程在执行在python中,那假如咱们试一试使用多进程,来观察一下多进程与多线程的区别。

多进程模块 multiprocessing模块

多进程模块与threading 模块用法类似,multiprocessing模块能够提供本地和远程的并发性,有效的经过GIL(全局解释所),来使用进程而不是线程。在多核CPU中使用多线程并不能有效利用CPU的优点。因为每个CPU中的进程只能在某一时间执行一个线程,因此此时能够考虑多进程来利用多核CPU,UNIX&WIN都是支持的。

将threading修改为multiprocessing便可。

结果:注意为了区别进程号我在结果中加了打印PID number因此显示结果以下。

这样能够明显区别开。

PID就是相似于身份证的一个东西,用来表示进程。一个进程在结束前都为一个不变的PID num,可是同一进程能够有不一样的PID num,好比多运行两次此程序你会发现PID num一直在变,或者在本身电脑上运行wechat,反复开关几回你也会发现都是不一样的PID num。

由上面的程序咱们能够发现,multiprocessing与threading的用法没什么大差异,也有start()、run()、join()等方法。

看一下文档里面multiprocessing的用法,以下图。

target表示调用的对象,args表示调用对象的文职参数元组,kwargs表示调用对象的字典,name为别名,Group基本用不上,为None也无所谓。

若是去掉PID num,那个从res中彻底看不出multiprocessing与threading有什么区别。*

multiprocessing 之 Queue与pipe:

因为线程共享数据,因此不须要通讯,而这点以前也提到过因此,并发进程的状况下,进程就会须要进程通讯(IPC机制)。

常见支持IPC的类 Queue&Pipe传送常见的对象。 (1)pipe是单向的,也能够是双向的。经过multiprocessing.Pipe(duplex=False)建立单向管道(默认为双向,相似于半双工全双工的意思,即单为只容许单向传输,双向则容许双向传输。)

pipe对象默认是双向的。在其创建的时候,返回一个含有两个元素的列表,每一个元素表明pipe的一端(Connection对象)。这就成告终果中的对暗号,‘天王盖地虎,小鸡炖蘑菇...不对 宝塔镇河妖。’

send方法发送,另外一端用recv方法来接收。

(2)Queue类与Pipe相似,不过学过数据结构,你们都知道队列都是先进先出。Queue容许多个进程放入,多个进程从队列存取。

这里最须要搞懂的地方就是这个锁的用处,就好像把进程们出入的时候带了手铐不让乱跑,进一个出一个明确,这样打印起来就不是很乱。

相关文章
相关标签/搜索