继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

先吃饭再喝汤?还是同时进行?Python多线程和多进程也是同理!

www说
关注TA
已关注
手记 439
粉丝 83
获赞 493

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

2.线程

线程或轻量级进程(同义)与进程类似,不过它们是在统一进程下执行的,并且共享相同的上下文,可以理解一个进程池中的东西线程共享。

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

这样难免会联想出问题,如果共享数据,那么会有风险。

Q1:

如果两个或多个线程访问同一片数据,由于数据访问顺序不同,可能导致结果不一致。这种情况就是“竞态条件”。不过大多数线程库都有一些同步原语,以允许线程管理器控制和访问。

Q2 :

当然啦 吃饭总会有自己爱吃的菜,所以每道菜的摄入量不会公平。也就是说线程无法给予公平的执行时间。如果没有专门的多线程情况进行修改,会导致CPU的时间分配向贪婪函数倾斜。

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

可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。

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

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

那么是不是python的多线程就完全没用了呢?

在这里我们进行分类讨论:

1、CPU密集型代码(各种循环处理、计数等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。

2、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

结果:

解析:

结果:

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

在实际开发中,我们这种利用线程的方式肯定不是我们常用的方式,因为自己在用的时候会传入很多参数,所以我们就必须用到继承Threading,来创建我们自用的线程类

结果同之前的就不在展示。

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

这样可以明显区别开。

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

由上面的程序我们可以发现,multiprocessing与threading的用法没什么大差别,也有start()、run()、join()等方法。

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

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

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

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

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

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

看懂了吗?学会了吗?学会就点歌关注吧!



作者:Python树苗
链接:https://www.jianshu.com/p/064d510dd961
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP