內(nèi)容簡述:
線程與進(jìn)程的相關(guān)概念
- 1、程序,進(jìn)程困介,線程大审,多進(jìn)程,多線程
- 2座哩、線程的生命周期
- 3徒扶、并行與并發(fā),同步與異步
- 4根穷、線程同步安全
- 5姜骡、與鎖有關(guān)的特殊情況:死鎖,饑餓與活鎖
- 6屿良、守護(hù)線程
- 7圈澈、線程并發(fā)的經(jīng)典問題:生產(chǎn)中與消費(fèi)者問題
- 8、Python中的GIL鎖
- 9尘惧、Python中對多線程與多進(jìn)程的支持
線程與進(jìn)程的相關(guān)概念
關(guān)于線程和進(jìn)程的話題康栈,大部分的書只是微微提下,讀者學(xué)完云里霧里喷橙,不知所以啥么。本章會對Python中的多線程和多進(jìn)程進(jìn)行詳解。大部分都是概念性的東西贰逾,不要去死記硬背悬荣,學(xué)完了解有個大概印象就好。
1疙剑、程序氯迂,進(jìn)程,線程言缤,多進(jìn)程嚼蚀,多線程
關(guān)于程序,進(jìn)程和線程的一些名詞概念如圖所示:
[圖片上傳失敗...(image-85ba8e-1555409468002)]
有句非常經(jīng)典的話:“進(jìn)程是資源分配的最小單位轧简,線程則是CPU調(diào)度的最小單位”驰坊。
先說說「多進(jìn)程」:從普通用戶的視角:
如果你的電腦是Windows的話,Ctrl+Alt+Del打開任務(wù)管理器哮独,可以看到電腦運(yùn)行著很多的進(jìn)程拳芙,比如QQ,微信皮璧,網(wǎng)易云音樂等舟扎。這就是多進(jìn)程,每個進(jìn)程各司其職完成對應(yīng)的功能悴务,互不干擾睹限,你聊天的時候音樂照常播放譬猫。
再說說開發(fā)仔的視角:
多進(jìn)程的概念更傾向于:多個進(jìn)程協(xié)同地區(qū)完成同一項(xiàng)工作。
問題:為什么要在應(yīng)用里使用多進(jìn)程羡疗?
筆者觀點(diǎn):擺脫系統(tǒng)的一些限制和為自己的應(yīng)用獲取更多的資源染服,舉個例子:
在Android系統(tǒng)中會為每個應(yīng)用(進(jìn)程)限制最大內(nèi)容,單個進(jìn)程超過這個閾值會引起OOM叨恨,而使用多進(jìn)程技術(shù)可以規(guī)避這個內(nèi)存溢出的問題柳刮。再舉個例子:Python在實(shí)現(xiàn)Python解析器(CPython)時引入了GIL鎖,使得任何時候僅有
一個線程在執(zhí)行痒钝,多線程的效率可能還比不上單線程秉颗,使用多進(jìn)程技術(shù)可以
規(guī)避這個限制。
再說說「多線程」送矩,首先為什么會引入線程的概念呢蚕甥?舉個例子:
你有一個文本程序,三個功能組成部分:接收用戶的輸入栋荸,顯示到屏幕上菇怀,保存到硬盤里,如果由三個進(jìn)程組成:輸入接收進(jìn)程A蒸其,顯示內(nèi)容進(jìn)程B敏释,寫入硬盤進(jìn)程C,而他們之間共同需要擁有的東西——文本內(nèi)容摸袁,而進(jìn)程A,B义屏,C運(yùn)行在不同的內(nèi)存空間靠汁,這就涉及到
進(jìn)程通信問題
了,而頻繁
的進(jìn)程切換勢必導(dǎo)致性能上的損失闽铐。有沒有一種機(jī)制使得做這三個任務(wù)時共享資源呢蝶怔?這個時候線程(輕量級的進(jìn)程)就派上用場了,多個線程共享進(jìn)程數(shù)據(jù)兄墅。相信讀者看到這里踢星,對于多進(jìn)程和多線程
的概念應(yīng)該有個初步的了解了,接下來簡單比較下兩者的優(yōu)劣和使用場景:
簡書
2隙咸、線程的生命周期
線程的生命周期如圖所示:
[圖片上傳失敗...(image-bc721e-1555409468002)]
各個狀態(tài)說明:
- New(新建)沐悦,新創(chuàng)建的線程進(jìn)過初始化,進(jìn)入Runnable(就緒)狀態(tài)五督。
- Runnable(就緒)藏否,等待線程調(diào)度,調(diào)度后進(jìn)入Running(運(yùn)行)狀態(tài)充包。
-
Running(運(yùn)行)副签,線程正常運(yùn)行,期間可能會因?yàn)槟承┣闆r進(jìn)入Blocked
(同步鎖;調(diào)用了sleep()和join()方法進(jìn)入Sleeping狀態(tài)淆储;執(zhí)行wait()方法進(jìn)入
Waiting狀態(tài)冠场,等待其他線程notify通知喚醒)。 - Blocked(堵塞)本砰,線程暫停運(yùn)行慈鸠,解除堵塞后進(jìn)入Runnable(就緒)狀態(tài)重新等待調(diào)度。
- Dead(死亡):線程完成了它的任務(wù)正常結(jié)束或因異常導(dǎo)致終止灌具。
3青团、并行與并發(fā),同步與異步
并行與并發(fā)的區(qū)別:
并行是同時處理多個任務(wù)咖楣,而并發(fā)則是處理多個任務(wù)督笆,而不一定要同時医增,并行可以說是并發(fā)的子集天通。
同步與異步的區(qū)別:
- 同步:線程執(zhí)行某個請求,如果該請求需要一段時間才能返回信息黍衙,那么這個線程
會一直等待珠十,直到收到返回信息才能繼續(xù)執(zhí)行下去料扰。- 異步:線程執(zhí)行完某個請求,不需要一直等焙蹭,直接繼續(xù)執(zhí)行后續(xù)操作晒杈,當(dāng)有消息
返回時系統(tǒng)會通知線程進(jìn)程處理,這樣可以提高執(zhí)行的效率孔厉;異步在網(wǎng)絡(luò)請求
的應(yīng)用非常常見拯钻。
4、線程同步安全
什么是線程同步安全問題撰豺?
當(dāng)有兩個或以上線程在同一時刻訪問同一資源粪般,可能會帶來一些問題。
比如:數(shù)據(jù)庫表不允許插入重復(fù)數(shù)據(jù)污桦,而線程1,2都得到了數(shù)據(jù)X亩歹,然后線程1,2同時查詢了數(shù)據(jù)庫,發(fā)現(xiàn)沒有數(shù)據(jù)X凡橱,接著兩線程都往數(shù)據(jù)庫中插入了X小作,然后就出現(xiàn)異常了,這就是線程的同步安全問題梭纹,而這里的數(shù)據(jù)庫資源我們又稱為:臨界資源(共享資源)躲惰。
如何解決同步安全問題(同步鎖)?
當(dāng)多個線程訪問臨界資源的時候变抽,有可能會出現(xiàn)線程安全問題础拨;而基本所有并發(fā)模式在解決線程安全問題時都采用"系列化訪問臨界資源"的方式氮块,就是同一時刻,只能有一個線程訪問臨界資源诡宗,也稱"同步互斥訪問"滔蝉。通常的操作就是加鎖(同步鎖),當(dāng)有線程訪問臨界資源時需要獲得這個鎖塔沃,其他線程無法訪問蝠引,只能等待(堵塞),等這個線程使用完釋放鎖蛀柴,供其他線程繼續(xù)訪問螃概。
5、與鎖有關(guān)的特殊情況:死鎖鸽疾,饑餓與活鎖
有了同步鎖并不意味著就一了百了了吊洼,當(dāng)多個進(jìn)程/線程的操作涉及到了多個鎖,
就可能出現(xiàn)下述三種情況:
- 死鎖(DeadLock)
兩個或以上進(jìn)程(線程)在執(zhí)行過程中制肮,因爭奪資源而造成的一種互相等待的現(xiàn)象冒窍,如果無外力作用,他們將繼續(xù)這樣僵持下去豺鼻。舉個形象化的例子:
開一個門需要兩條鑰匙综液,而兩個人手上各持有一條,然后都不愿意把自己的鑰匙給對方儒飒,就一直那樣僵持著谬莹,這種狀態(tài)就叫死鎖。
死鎖發(fā)生的條件:
- 互斥條件(臨界資源)约素;
- 請求和保持條件(請求資源但不釋放自己暫用的資源)届良;
- 不剝奪條件(線程獲得的資源只有線程使用完后自己釋放,不能被其他線程剝奪)圣猎;
- 環(huán)路等待條件:在死鎖發(fā)生時,必然存在一個“進(jìn)程-資源環(huán)形鏈”乞而,t1等t2送悔,t2等t1;
如何避免死鎖:
破壞四個條件中的一個或多個條件爪模,常見的預(yù)防方法有如下兩種:
① 有序資源分配法:資源按某種規(guī)則統(tǒng)一編號欠啤,申請時必須按照升序申請: 屬于同一類的資源要一次申請完,申請不同類資源按照一定的順序申請屋灌。
② 銀行家算法:就是檢查申請者對資源的最大需求量洁段,如果當(dāng)前各類資源都可以滿足的 申請者的請求,就滿足申請者的請求共郭,這樣申請者就可很快完成其計(jì)算祠丝,然后釋放它占用 的資源疾呻,從而保證了系統(tǒng)中的所有進(jìn)程都能完成,所以可避免死鎖的發(fā)生写半。 理論上能夠非常有效的避免死鎖岸蜗,但從某種意義上說,缺乏使用價值叠蝇,因?yàn)楹苌儆羞M(jìn)程能夠知道所需資源的最大值璃岳,而且進(jìn)程數(shù)目也不是固定的,往往是不斷變化的悔捶, 況且原本可用的資源也可能突然間變得不可用(比如打印機(jī)損壞)铃慷。
- 2.饑餓(starvation)與餓死(starve to death)
資源分配策略有可能是不公平的,即不能保證等待時間上界的存在蜕该,即使沒有發(fā)生死鎖犁柜, 某些進(jìn)程可能因長時間的等待,對進(jìn)程推進(jìn)與相應(yīng)帶來明顯影響蛇损,此時的進(jìn)程就是 發(fā)生了進(jìn)程饑餓(starvation)赁温,當(dāng)饑餓達(dá)到一定程度即使此時進(jìn)程即使完成了任務(wù)也沒有實(shí)際意義時,此時稱該進(jìn)程被餓死(starve to death)淤齐,舉個典型的例子:文件打印采用短文件優(yōu)先策略股囊,如果短文件太多,長文件會一直推遲更啄,那還打印個毛稚疹。
- 3.活鎖(LiveLock)
特殊的饑餓,一系列進(jìn)程輪詢等待某個不可能為真的條件為真祭务,此時進(jìn)程不會進(jìn)入blocked狀態(tài)内狗,但會占用CPU資源,活鎖還有幾率能自己解開义锥,而死鎖則無法自己解開柳沙。(例子:都覺得對方優(yōu)先級比自己高,相互謙讓拌倍,導(dǎo)致無法使用某資源)赂鲤,簡單避免活鎖的方法:先來先服務(wù)策略。
6柱恤、守護(hù)線程
又稱「后臺線程」数初,是一種為其他線程提供服務(wù)的線程,比如一個簡單的例子:你有兩個線程在協(xié)同的做一件事梗顺,如果有一個線程死掉泡孩,事情就無法繼續(xù)下去,此時可以引入守護(hù)線程寺谤,輪詢地去判斷兩個線程是否活著(調(diào)isAlive())仑鸥,如果死掉就start開啟線程吮播,在Python中可以在線程初始化的時候調(diào)用
setDaemon(True)
把線程設(shè)置為守護(hù)線程,另外如果程序中只剩下守護(hù)線程的話程序會自動退出锈候。
7薄料、線程并發(fā)的經(jīng)典問題:生產(chǎn)中與消費(fèi)者問題
說到線程并發(fā),不得不說的一個經(jīng)典問題就是:生產(chǎn)中與消費(fèi)者問題:
兩個共享固定緩沖區(qū)大小的線程泵琳,生產(chǎn)者線程負(fù)責(zé)生產(chǎn)一定量的數(shù)據(jù) 放入緩沖區(qū)摄职, 而消費(fèi)者線程則負(fù)責(zé)消耗緩沖區(qū)中的數(shù)據(jù),關(guān)鍵問題是需要保證兩點(diǎn):
- 緩沖區(qū)滿的時候获列,生產(chǎn)者不再往緩沖區(qū)中填充數(shù)據(jù)谷市。
- 緩存區(qū)空的時候,消費(fèi)者不在消耗緩沖區(qū)中的數(shù)據(jù)击孩。
8迫悠、Python中的GIL鎖
上面講到Python在實(shí)現(xiàn)Python解析器(CPython)時引入了GIL鎖,使得「任何時候僅有 一個線程在執(zhí)行」巩梢,Python多線程的效率可能還比不上單線程创泄,那么這個GIL鎖是什么?
概念:全局解釋器鎖括蝠,用于同步線程的一種機(jī)制鞠抑,使得任何時候僅有一個線程在執(zhí)行。GIL 并不是Python的特性忌警,只是在實(shí)現(xiàn)Python解析器(CPython)時引入的一個概念搁拙。換句話說,Python完全可以不依賴于GIL法绵。Python解釋器進(jìn)程內(nèi)的多線程是以協(xié)作多任務(wù)方式執(zhí)行的箕速,當(dāng)一個線程遇到I/O操作時會釋放GIL,而依賴CPU計(jì)算的線程則是執(zhí)行代碼量到一定的閥值,才會釋放GIL朋譬。
而在Python 3.2開始使用新的GIL盐茎,使用固定的超時時間來指示當(dāng)前線程放棄全局鎖,就是:「當(dāng)前線程持有這個鎖徙赢,且其他線程請求這個鎖時庭呜,當(dāng)前線程就會在5毫秒后被強(qiáng)制釋放掉該鎖∠溃」多線程在處理CPU密集型操作因?yàn)楦鞣N循環(huán)處理計(jì)數(shù)等,會很快達(dá)到閥值扶关,而**多個線程來回切換是會消耗資源的阴汇,所以多線程的效率往往可能還比不上單線程!
而在多核CPU上效率會更低节槐,因?yàn)槎嗪谁h(huán)境下搀庶,持有鎖的CPU釋放鎖后拐纱,其他CPU上的線程都會進(jìn)行競爭,但GIL可能馬上又會被之前的CPU拿到拿到哥倔,導(dǎo)致其他幾個CPU上被喚醒后的線程會醒著等待到切換時間后又進(jìn)入待調(diào)度狀態(tài)秸架,從而造成 線程顛簸(thrashing),導(dǎo)致效率更低咆蒿。
問題:因?yàn)镚IL鎖的原因东抹,對于CPU密集型操作,Python多線程就是雞肋了沃测?
答:是的缭黔!盡管多線程開銷小,但卻無法利用多核優(yōu)勢蒂破!可以使用多進(jìn)程來規(guī)避這個問題馏谨,Python提供了
multiprocessing
這個跨平臺的模塊來幫助我們實(shí)現(xiàn)多進(jìn)程代碼的編寫。每個進(jìn)程都有自己獨(dú)立的GIL附迷,因此不會出現(xiàn)進(jìn)程間GIL鎖搶奪的問題惧互,但是也增加程序?qū)崿F(xiàn)線程間數(shù)據(jù)通訊和同步時的成本,這個需要自行進(jìn)行權(quán)衡喇伯。
9喊儡、Python中對多線程與多進(jìn)程的支持
Python與線程,進(jìn)程相關(guān)的官方文檔鏈接:docs.python.org/3/library/c…
簡單說下這些模塊都是干嘛的:
- threading—— 提供線程相關(guān)的操作艘刚。
- multiprocessing—— 提供進(jìn)程程相關(guān)的操作管宵。
- concurrent.futures—— 異步并發(fā)模塊,實(shí)現(xiàn)多線程和多進(jìn)程的異步并發(fā)(3.2后引入)攀甚。
- subprocess—— 創(chuàng)建子進(jìn)程箩朴,并提供鏈接到他們輸入/輸出/錯誤管道的方法,并獲得他們的返回碼秋度,該模塊旨在替換幾個較舊的模塊和功能:os.system與os.spawn*炸庞。
- sched——任務(wù)調(diào)度(延時處理機(jī)制)。
- queue——提供同步的荚斯、線程安全的隊(duì)列類埠居。
還有幾個是兼容模塊,比如Python 2.x上用threading和Python 3.x上用thread:
- dummy_threading:提供和threading模塊相同的接口事期,2.x使用threading兼容滥壕。
- _thread:threading模塊的基礎(chǔ)模塊,應(yīng)該盡量使用 threading 模塊替代兽泣。
- dummy_thread:提供和thread模塊相同的接口绎橘,3.x使用threading兼容。