Python多线程与多进程

简书

内容简述:

线程与进程的相关概念

  • 1、程序,进程,线程,多进程,多线程
  • 2、线程的生命周期
  • 3、并行与并发,同步与异步
  • 4、线程同步安全
  • 5、与锁有关的特殊情况:死锁,饥饿与活锁
  • 6、守护线程
  • 7、线程并发的经典问题:生产中与消费者问题
  • 8、Python中的GIL锁
  • 9、Python中对多线程与多进程的支持

线程与进程的相关概念

关于线程和进程的话题,大部分的书只是微微提下,读者学完云里雾里,不知所以。本章会对Python中的多线程和多进程进行详解。大部分都是概念性的东西,不要去死记硬背,学完了解有个大概印象就好。


1、程序,进程,线程,多进程,多线程

关于程序,进程和线程的一些名词概念如图所示:
[图片上传失败...(image-85ba8e-1555409468002)]
有句非常经典的话:“进程是资源分配的最小单位,线程则是CPU调度的最小单位”。

先说说「多进程」:从普通用户的视角:

如果你的电脑是Windows的话,Ctrl+Alt+Del打开任务管理器,可以看到电脑运行着很多的进程,比如QQ,微信,网易云音乐等。这就是多进程,每个进程各司其职完成对应的功能,互不干扰,你聊天的时候音乐照常播放。

再说说开发仔的视角:

多进程的概念更倾向于:多个进程协同地区完成同一项工作。

问题:为什么要在应用里使用多进程

笔者观点:摆脱系统的一些限制和为自己的应用获取更多的资源,举个例子:
在Android系统中会为每个应用(进程)限制最大内容,单个进程超过这个阈值会引起OOM,而使用多进程技术可以规避这个内存溢出的问题。再举个例子:Python在实现Python解析器(CPython)时引入了GIL锁,使得任何时候仅有
一个线程在执行,多线程的效率可能还比不上单线程,使用多进程技术可以
规避这个限制。

再说说「多线程」,首先为什么会引入线程的概念呢?举个例子:

你有一个文本程序,三个功能组成部分:接收用户的输入,显示到屏幕上,保存到硬盘里,如果由三个进程组成:输入接收进程A,显示内容进程B,写入硬盘进程C,而他们之间共同需要拥有的东西——文本内容,而进程A,B,C运行在不同的内存空间,这就涉及到进程通信问题了,而频繁
的进程切换势必导致性能上的损失。有没有一种机制使得做这三个任务时共享资源呢?这个时候线程(轻量级的进程)就派上用场了,多个线程共享进程数据。相信读者看到这里,对于多进程和多线程
的概念应该有个初步的了解了,接下来简单比较下两者的优劣和使用场景:

简书

2、线程的生命周期

线程的生命周期如图所示

[图片上传失败...(image-bc721e-1555409468002)]

各个状态说明

  • New(新建),新创建的线程进过初始化,进入Runnable(就绪)状态。
  • Runnable(就绪),等待线程调度,调度后进入Running(运行)状态。
  • Running(运行),线程正常运行,期间可能会因为某些情况进入Blocked
    (同步锁;调用了sleep()和join()方法进入Sleeping状态;执行wait()方法进入
    Waiting状态,等待其他线程notify通知唤醒)。
  • Blocked(堵塞),线程暂停运行,解除堵塞后进入Runnable(就绪)状态重新等待调度。
  • Dead(死亡):线程完成了它的任务正常结束或因异常导致终止。

3、并行与并发,同步与异步

并行与并发的区别

并行是同时处理多个任务,而并发则是处理多个任务,而不一定要同时,并行可以说是并发的子集

同步与异步的区别

  • 同步:线程执行某个请求,如果该请求需要一段时间才能返回信息,那么这个线程
    一直等待,直到收到返回信息才能继续执行下去。
  • 异步:线程执行完某个请求,不需要一直等直接继续执行后续操作,当有消息
    返回时系统会通知线程进程处理,这样可以提高执行的效率;异步在网络请求
    的应用非常常见。

4、线程同步安全

什么是线程同步安全问题

当有两个或以上线程在同一时刻访问同一资源,可能会带来一些问题。
比如:数据库表不允许插入重复数据,而线程1,2都得到了数据X,然后线程1,2同时查询了数据库,发现没有数据X,接着两线程都往数据库中插入了X,然后就出现异常了,这就是线程的同步安全问题,而这里的数据库资源我们又称为:临界资源(共享资源)

如何解决同步安全问题(同步锁)?

当多个线程访问临界资源的时候,有可能会出现线程安全问题;而基本所有并发模式在解决线程安全问题时都采用"系列化访问临界资源"的方式,就是同一时刻,只能有一个线程访问临界资源,也称"同步互斥访问"。通常的操作就是加锁(同步锁),当有线程访问临界资源时需要获得这个锁,其他线程无法访问,只能等待(堵塞),等这个线程使用完释放锁,供其他线程继续访问。


5、与锁有关的特殊情况:死锁,饥饿与活锁

有了同步锁并不意味着就一了百了了,当多个进程/线程的操作涉及到了多个锁,
就可能出现下述三种情况:

  • 死锁(DeadLock)

两个或以上进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,如果无外力作用,他们将继续这样僵持下去。举个形象化的例子:

开一个门需要两条钥匙,而两个人手上各持有一条,然后都不愿意把自己的钥匙给对方,就一直那样僵持着,这种状态就叫死锁。

死锁发生的条件

  • 互斥条件(临界资源);
  • 请求和保持条件(请求资源但不释放自己暂用的资源);
  • 不剥夺条件(线程获得的资源只有线程使用完后自己释放,不能被其他线程剥夺);
  • 环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,t1等t2,t2等t1;

如何避免死锁

破坏四个条件中的一个或多个条件,常见的预防方法有如下两种:

① 有序资源分配法:资源按某种规则统一编号,申请时必须按照升序申请: 属于同一类的资源要一次申请完,申请不同类资源按照一定的顺序申请。

② 银行家算法:就是检查申请者对资源的最大需求量,如果当前各类资源都可以满足的 申请者的请求,就满足申请者的请求,这样申请者就可很快完成其计算,然后释放它占用 的资源,从而保证了系统中的所有进程都能完成,所以可避免死锁的发生。 理论上能够非常有效的避免死锁,但从某种意义上说,缺乏使用价值,因为很少有进程能够知道所需资源的最大值,而且进程数目也不是固定的,往往是不断变化的, 况且原本可用的资源也可能突然间变得不可用(比如打印机损坏)。

  • 2.饥饿(starvation)与饿死(starve to death)

资源分配策略有可能是不公平的,即不能保证等待时间上界的存在,即使没有发生死锁, 某些进程可能因长时间的等待对进程推进与相应带来明显影响,此时的进程就是 发生了进程饥饿(starvation),当饥饿达到一定程度即使此时进程即使完成了任务也没有实际意义时,此时称该进程被饿死(starve to death),举个典型的例子:文件打印采用短文件优先策略,如果短文件太多,长文件会一直推迟,那还打印个毛。

  • 3.活锁(LiveLock)

特殊的饥饿,一系列进程轮询等待某个不可能为真的条件为真,此时进程不会进入blocked状态,但会占用CPU资源,活锁还有几率能自己解开,而死锁则无法自己解开。(例子:都觉得对方优先级比自己高,相互谦让,导致无法使用某资源),简单避免活锁的方法:先来先服务策略


6、守护线程

又称「后台线程」,是一种为其他线程提供服务的线程,比如一个简单的例子:你有两个线程在协同的做一件事,如果有一个线程死掉,事情就无法继续下去,此时可以引入守护线程,轮询地去判断两个线程是否活着(调isAlive()),如果死掉就start开启线程,在Python中可以在线程初始化的时候调用setDaemon(True)把线程设置为守护线程,另外如果程序中只剩下守护线程的话程序会自动退出。


7、线程并发的经典问题:生产中与消费者问题

说到线程并发,不得不说的一个经典问题就是:生产中与消费者问题

两个共享固定缓冲区大小的线程,生产者线程负责生产一定量的数据 放入缓冲区, 而消费者线程则负责消耗缓冲区中的数据,关键问题是需要保证两点:

  • 缓冲区满的时候,生产者不再往缓冲区中填充数据。
  • 缓存区空的时候,消费者不在消耗缓冲区中的数据。

8、Python中的GIL锁

上面讲到Python在实现Python解析器(CPython)时引入了GIL锁,使得「任何时候仅有 一个线程在执行」,Python多线程的效率可能还比不上单线程,那么这个GIL锁是什么?

概念:全局解释器锁,用于同步线程的一种机制,使得任何时候仅有一个线程在执行。GIL 并不是Python的特性,只是在实现Python解析器(CPython)时引入的一个概念。换句话说,Python完全可以不依赖于GILPython解释器进程内的多线程是以协作多任务方式执行的,当一个线程遇到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锁抢夺的问题,但是也增加程序实现线程间数据通讯和同步时的成本,这个需要自行进行权衡。


9、Python中对多线程与多进程的支持

Python与线程,进程相关的官方文档链接:docs.python.org/3/library/c…

简单说下这些模块都是干嘛的:

  • threading—— 提供线程相关的操作。
  • multiprocessing—— 提供进程程相关的操作。
  • concurrent.futures—— 异步并发模块,实现多线程和多进程的异步并发(3.2后引入)。
  • subprocess—— 创建子进程,并提供链接到他们输入/输出/错误管道的方法,并获得他们的返回码,该模块旨在替换几个较旧的模块和功能:os.system与os.spawn*。
  • sched——任务调度(延时处理机制)。
  • queue——提供同步的、线程安全的队列类。

还有几个是兼容模块,比如Python 2.x上用threading和Python 3.x上用thread:

  • dummy_threading:提供和threading模块相同的接口,2.x使用threading兼容。
  • _thread:threading模块的基础模块,应该尽量使用 threading 模块替代。
  • dummy_thread:提供和thread模块相同的接口,3.x使用threading兼容。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 一. 操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式: 向下管理硬件,向上提供接口.操作系统进行...
    月亮是我踢弯得阅读 5,950评论 3 28
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 概念理解 对于操作系统来说,一个任务就是一个进程。例如打开浏览器,打开word,打开记事本等等,都是独立的任务,它...
    忘了呼吸的那只猫阅读 425评论 0 1
  • Python是运行在解释器中的语言,查找资料知道,python中有一个全局锁(GIL),在使用多线程(Thread...
    冬季恋歌1218阅读 879评论 0 2
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,525评论 0 5