引言html
从刚开始学习Python爬虫的时候,就一直惦记着多线程这个东西, 想一想每次下载图片都是单线程,一个下完继续下一个,多呆啊!python
没占满的带宽(10M带宽),1%的CPU占用率(笔者的是i7 6700K),要不要 那么浪费,因此,不搞点多线程,多进程,协程这样的东西提升下资源利用 率,怎么也说不过去吧?然而关于线程这种话题,通常都是会让不少新手 玩家望而却步,并且据说Python里还有什么全局解释器锁(GIL),搞得Py没法 实现高效的多线程,一听就感受很难:git
虚个卵子哦,跟着小猪把Python里和多线程相关的东西都撸一遍吧! 本节主要是对一些概念进行了解~github
多线程与多进程的理解:算法
操做系统原理相关的书,基本都会提到一句很经典的话: "进程是资源分配的最小单位,线程则是CPU调度的最小单位"。数据库
说到进程,若是你是windows的电脑的话,Ctrl+Alt+Del打开任务 管理器,能够看到当前电脑上正在运行的不少个进程,网易云啊, QQ,微信啊,等等;这就是多进程,只是每一个进程各司其职完成 对应的功能而已,播放、聊天,互不干扰。这是吃瓜群众的见解, 而对于咱们开发来讲,多进程的概念更倾向于:多个进程协同地去完成 同一项工做,为何要在应用里使用多线程,我的的见解以下: 为了摆脱系统的一些限制和为本身的应用获取更多的资源,举个例子: 在Android中为每一个应用(进程)限制类最大内存,单个进程超过这个 阀值是会OOM的,而使用多进程技术能够减小内存溢出的问题; 再举个例子:Python在实现Python解析器(CPython)时引入GIL锁 这种东西,使得任什么时候候仅有一个线程在执行,多线程的效率还 可能比不上单线程,使用多线程能够规避这个限制。编程
说完多进程,而后说下多线程,首先为什么会引入线程呢?举个例子: 你有一个文本程序,接收用户的输入,显示到屏幕上,并保存到硬盘里, 由三个进程组成:输入接收进程A,显示内容进程B,写入硬盘进程C, 而他们之间共同须要要拥有的东西——文本内容,由于进程A,B,C 运行在不一样的内存空间,这就涉及到进程通讯问题了,而频繁的切换 势必致使性能上的损失。有没有一种机制使得作这三个任务时共享资源呢? 这个时候线程(轻量级的进程)就粉墨登场啦!感受就像进程又开辟了 一个小世界同样:系统 -> 进程 -> 线程,系统里有不少进程,进程里 又有不少线程。(有点像斗破小说那种套路...)windows
相信到这里你对多进程和多线程的概念就应一清二楚了,简单比较下 二者的区别与使用场景吧:(摘自:浅谈多进程多线程的选择)缓存
对比维度 | 多进程 | 多线程 |
---|---|---|
数据共享、同步 | 数据共享复杂,须要用IPC; 数据是分开的,同步简单 |
共享进程数据,数据共享简单, 但也是由于这个缘由致使同步复杂 |
内存、CPU | 占用内存多,切换复杂,CPU利用率低 | 占用内存少,切换简单,CPU利用率高 |
建立销毁、切换 | 建立销毁、切换复杂,速度慢 | 建立销毁、切换简单,速度很快 |
编程、调试 | 编程简单,调试简单 | 编程复杂,调试复杂 |
可靠性 | 进程间不会互相影响 | 一个线程挂掉将致使整个进程挂掉 |
分布式 | 适应于多核、多机分布式;若是一台 机器不够,扩展到多台机器比较简单 |
适应于多核分布式 |
各个状态说明:安全
并行是同时处理多个任务,而并发则是处理多个任务,而不必定要同时, 并行能够说是并发的子集。
同步:线程执行某个请求,若是该请求须要一段时间才能返回信息, 那么这个线程会一直等待,直到收到返回信息才能继续执行下去;
异步:线程执行完某个请求,不须要一直等,直接继续执行后续操做, 当有消息返回时系统会通知线程进程处理,这样能够提升执行的效率; 异步在网络请求的应用很是常见~
当有两个或以上线程在同一时刻访问同一资源,可能会带来一些问题, 好比:数据库表不容许插入重复数据,而线程1,2都获得了数据X,而后 线程1,2同时查询了数据库,发现没有数据X,接着两线程都往数据库中 插入了X,而后就GG啦,这就是线程的同步安全问题,而这里的数据库 资源咱们又称为:临界资源(共享资源)。
当多个线程访问临界资源的时候,有可能会出现线程安全问题; 而基本全部并发模式在解决线程安全问题时都采用"系列化访问 临界资源"的方式,就是同一时刻,只能有一个线程访问临界资源, 也称"同步互斥访问"。一般的操做就是加锁(同步锁),当有线程访问 临界资源时须要得到这个锁,其余线程没法访问,只能等待(堵塞), 等这个线程使用完释放锁,供其余线程继续访问。
有了同步锁不意味着就一了百了了,当多个进程/线程的操做涉及到了多个锁, 就可能出现下述三种状况:
两个或以上进程(线程)在执行过程当中,因争夺资源而形成的一种互相等待的现象, 若是无外力做用,他们将继续这样僵持下去;简单点说:两我的互相持有对方想要的资源, 而后每一方都不肯意放弃本身手上的资源,就一直那样僵持着。
死锁发生的条件:
互斥条件(临界资源); 请求和保持条件(请求资源但不释放本身暂用的资源); 不剥夺条件(线程得到的资源只有线程使用完后本身释放,不能被其余线程剥夺); 环路等待条件:在死锁发生时,必然存在一个”进程-资源环形链”,t1等t2,t2等t1;
如何避免死锁:
破坏四个条件中的一个或多个条件,常见的预防方法有以下两种: 有序资源分配法:资源按某种规则统一编号,申请时必须按照升序申请: 1.属于同一类的资源要一次申请完;2.申请不一样类资源按照必定的顺序申请。 银行家算法:就是检查申请者对资源的最大需求量,若是当前各种资源均可以知足的 申请者的请求,就知足申请者的请求,这样申请者就可很快完成其计算,而后释放它占用 的资源,从而保证了系统中的全部进程都能完成,因此可避免死锁的发生。 理论上可以很是有效的避免死锁,但从某种意义上说,缺少使用价值,由于不多有进程 可以知道所需资源的最大值,并且进程数目也不是固定的,每每是不断变化的, 何况本来可用的资源也可能忽然间变得不可用(好比打印机损坏)。
资源分配策略有多是不公平的,即不能保证等待时间上界的存在,即便没有 发生死锁, 某些进程可能因长时间的等待,对进程推动与相应带来明显影响, 此时的进程就是 发生了进程饥饿(starvation),当饥饿达到必定程序即此时 进程即便完成了任务也 没有实际意义时,此时称该进程被饿死(starve to death), 典型的例子: 文件打印,采用短文件优先策略,若是短文件太多,长文件会一直 推迟,那还打印个毛。
特殊的饥饿,一系列进程轮询等待某个不可能为真的条件为真,此时进程不会 进入blocked状态, 但会占用CPU资源,活锁还有概率能本身解开,而死锁则 没法本身解开。(例子:都以为对方优先级比本身高,相互谦让,致使没法 使用某资源),简单避免死锁的方法:先来先服务策略。
也叫后台线程,是一种为其余线程提供服务的线程,好比一个简单的例子: 你有两个线程在协同的作一件事,若是有一个线程死掉,事情就没法继续 下去,此时能够引入守护线程,轮询地去判断两个线程是否或者(调isAlive()), 若是死掉就start开启线程,在Python中能够在线程初始化的时候调用 setDaemon(True)把线程设置为守护线程,若是程序中只剩下守护线程 的话会自动退出。
说到线程并发,不得不说的一个经典问题就是:生产中与消费者问题:
两个共享固定缓冲区大小的线程,生产者线程负责生产必定量的数据 放入缓冲区, 而消费者线程则负责消耗缓冲区中的数据,关键问题是 须要保证两点:
听不懂也没什么,这个后面会写例子的~
概念:
全局解释器锁,用于同步线程的一种机制,使得任什么时候候仅有一个线程在执行。 GIL 并非Python的特性,只是在实现Python解析器(CPython)时引入的 一个概念。换句话说,Python彻底能够不依赖于GIL。
Python解释器进程内的多线程是以协做多任务方式执行的,当一个线程遇到 I/O操做时会释放GIL。而依赖CPU计算的线程则是执行代码量到必定的阀值, 才会释放GIL。而在Python 3.2开始使用新的GIL,使用固定的超时时间来指示 当前线程放弃全局锁,就是:当前线程持有这个锁,且其余线程请求这个锁时, 当前线程就会再5毫秒后被强制释放掉该锁。
多线程在处理CPU密集型操做由于各类循环处理计数等,会很快达到阀值, 而多个线程来回切换是会消耗资源的,因此多线程的效率每每可能还比不上 单线程!而在多核CPU上效率会更低,由于多核环境下,持有锁的CPU释放锁后, 其余CPU上的线程都会进行竞争,但GIL可能立刻又会被以前的CPU拿到拿到, 致使其余几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度 状态,从而形成线程颠簸(thrashing),致使效率更低。
问题:
由于GIL锁的缘由,对于CPU密集型操做,Python多线程就是鸡肋了?
答:是的!尽管多线程开销小,但却没法利用多核优点! 可使用多进程来规避这个问题,Python提供了multiprocessing 这个跨平台的模块来帮助咱们实现多进程代码的编写。 每一个线程都有本身独立的GIL,所以不会出现进程间GIL 锁抢夺的问题,可是也增长程序实现线程间数据通信和同步 是的成本,这个须要自行进行权衡。
Python与线程,进程相关的官方文档: 17. Concurrent Execution docs.python.org/3/library/c…
简单介绍下里面的一些模块,后面会一个个啃~
还有几个是兼容模块,好比Python 2.x上用threading和Python 3.x上用thread:
本节咱们围绕着线程以及进程相关的概念进行了解析,尽管有些 枯燥,可是若是坚持看完,相信你对于线程与进程的理解会更进 一步,概念什么都是虚的,纸上得来终觉浅,绝知此事要躬行, 下节开始咱们来经过写代码的方式一一学习这些模块吧!
参考文献:
来啊,Py交易啊
想加群一块儿学习Py的能够加下,智障机器人小Pig,验证信息里包含: Python,python,py,Py,加群,交易,屁眼 中的一个关键词便可经过;
验证经过后回复 加群 便可得到加群连接(不要把机器人玩坏了!!!)~~~ 欢迎各类像我同样的Py初学者,Py大神加入,一块儿愉快地交流学♂习,van♂转py。