什么是协程?
這幾天一直在看協(xié)程相關(guān)的介紹,自動(dòng)駕駛系統(tǒng)Apollo中使用了協(xié)程作為底層調(diào)度單元,那么協(xié)程究竟是如何工作的呢?通過(guò)本文你可以了解到,為什么需要協(xié)程?以及使用協(xié)程有哪些注意事項(xiàng)?
為什么需要協(xié)程?
我們都知道多線程,當(dāng)需要同時(shí)執(zhí)行多項(xiàng)任務(wù)的時(shí)候,就會(huì)采用多線程并發(fā)執(zhí)行。拿手機(jī)支付舉例子,當(dāng)收到付款信息的時(shí)候,需要查詢(xún)數(shù)據(jù)庫(kù)來(lái)判斷余額是否充足,然后再進(jìn)行付款。
假設(shè)最開(kāi)始我們只有可憐的10個(gè)用戶(hù),收到10條付款消息之后,我們開(kāi)啟啟動(dòng)10個(gè)線程去查詢(xún)數(shù)據(jù)庫(kù),由于用戶(hù)量很少,結(jié)果馬上就返回了。第2天用戶(hù)增加到了100人,你選擇增加100個(gè)線程去查詢(xún)數(shù)據(jù)庫(kù),等到第三天,你們加大了優(yōu)惠力度,這時(shí)候有1000人同時(shí)在線付款,你按照之前的方法,繼續(xù)采用1000個(gè)線程去查詢(xún)數(shù)據(jù)庫(kù),并且隱隱覺(jué)察到有什么不對(duì)。
不斷增長(zhǎng)的線程
幾天之后,見(jiàn)勢(shì)頭大好,運(yùn)營(yíng)部門(mén)開(kāi)始不停的補(bǔ)貼消費(fèi)券,展開(kāi)了史無(wú)前例的大促銷(xiāo),你們的用戶(hù)開(kāi)始爆炸增長(zhǎng),這時(shí)候有10000人同時(shí)在線付款,你打算啟動(dòng)10000個(gè)線程來(lái)處理任務(wù)。等等,問(wèn)題來(lái)了,因?yàn)槊總€(gè)線程至少會(huì)占用4M的內(nèi)存空間,10000個(gè)線程會(huì)消耗39G的內(nèi)存,而服務(wù)器的內(nèi)存配置只有區(qū)區(qū)8G,這時(shí)候你有2種選擇,一是選擇增加服務(wù)器,二是選擇提高代碼效率。那么是否有方法能夠提高效率呢?
我們知道操作系統(tǒng)在線程等待IO的時(shí)候,會(huì)阻塞當(dāng)前線程,切換到其它線程,這樣在當(dāng)前線程等待IO的過(guò)程中,其它線程可以繼續(xù)執(zhí)行。當(dāng)系統(tǒng)線程較少的時(shí)候沒(méi)有什么問(wèn)題,但是當(dāng)線程數(shù)量非常多的時(shí)候,卻產(chǎn)生了問(wèn)題。一是系統(tǒng)線程會(huì)占用非常多的內(nèi)存空間,二是過(guò)多的線程切換會(huì)占用大量的系統(tǒng)時(shí)間。
線程切換
協(xié)程剛好可以解決上述2個(gè)問(wèn)題。協(xié)程運(yùn)行在線程之上,當(dāng)一個(gè)協(xié)程執(zhí)行完成后,可以選擇主動(dòng)讓出,讓另一個(gè)協(xié)程運(yùn)行在當(dāng)前線程之上。協(xié)程并沒(méi)有增加線程數(shù)量,只是在線程的基礎(chǔ)之上通過(guò)分時(shí)復(fù)用的方式運(yùn)行多個(gè)協(xié)程,而且協(xié)程的切換在用戶(hù)態(tài)完成,切換的代價(jià)比線程從用戶(hù)態(tài)到內(nèi)核態(tài)的代價(jià)小很多。
協(xié)程切換
回到上面的問(wèn)題,我們只需要啟動(dòng)100個(gè)線程,每個(gè)線程上運(yùn)行100個(gè)協(xié)程,這樣不僅減少了線程切換開(kāi)銷(xiāo),而且還能夠同時(shí)處理10000個(gè)讀取數(shù)據(jù)庫(kù)的任務(wù),很好的解決了上述任務(wù)。
知道了協(xié)程的工作方式,那么我們?cè)倏聪率褂脜f(xié)程有哪些注意事項(xiàng)。
協(xié)程的注意事項(xiàng)
實(shí)際上協(xié)程并不是什么銀彈,協(xié)程只有在等待IO的過(guò)程中才能重復(fù)利用線程,上面我們已經(jīng)講過(guò)了,線程在等待IO的過(guò)程中會(huì)陷入阻塞狀態(tài),意識(shí)到問(wèn)題沒(méi)有?
假設(shè)協(xié)程運(yùn)行在線程之上,并且協(xié)程調(diào)用了一個(gè)阻塞IO操作,這時(shí)候會(huì)發(fā)生什么?實(shí)際上操作系統(tǒng)并不知道協(xié)程的存在,它只知道線程,因此在協(xié)程調(diào)用阻塞IO操作的時(shí)候,操作系統(tǒng)會(huì)讓線程進(jìn)入阻塞狀態(tài),當(dāng)前的協(xié)程和其它綁定在該線程之上的協(xié)程都會(huì)陷入阻塞而得不到調(diào)度,這往往是不能接受的。
因此在協(xié)程中不能調(diào)用導(dǎo)致線程阻塞的操作。也就是說(shuō),協(xié)程只有和異步IO結(jié)合起來(lái),才能發(fā)揮最大的威力。
那么如何處理在協(xié)程中調(diào)用阻塞IO的操作呢?一般有2種處理方式:
協(xié)程對(duì)計(jì)算密集型的任務(wù)也沒(méi)有太大的好處,計(jì)算密集型的任務(wù)本身不需要大量的線程切換,因此協(xié)程的作用也十分有限,反而還增加了協(xié)程切換的開(kāi)銷(xiāo)。
以上就是協(xié)程的注意事項(xiàng)。這里順帶一提JavaScript的異步變同步的調(diào)用方式,如果協(xié)程能夠?qū)崿F(xiàn)該類(lèi)型的語(yǔ)法,不僅可以把異步操作變?yōu)橥?#xff0c;同時(shí)在IO操作的時(shí)候還能夠不占用CPU,寫(xiě)起來(lái)非常方便。
異步變同步的調(diào)用方式只是一種編程方式,不管是用線程還是用協(xié)程都可以實(shí)現(xiàn)這種編程方式,好處是不用在處理非常多的回調(diào)。
async function getProcessedData(url) {let v;try {v = await downloadData(url);} catch(e) {v = await downloadFallbackData(url);}try {return await processDataInWorker(v);} catch (e) {return null;} }總結(jié)
在有大量IO操作業(yè)務(wù)的情況下,我們采用協(xié)程替換線程,可以到達(dá)很好的效果,一是降低了系統(tǒng)內(nèi)存,二是減少了系統(tǒng)切換開(kāi)銷(xiāo),因此系統(tǒng)的性能也會(huì)提升。
在協(xié)程中盡量不要調(diào)用阻塞IO的方法,比如打印,讀取文件,Socket接口等,除非改為異步調(diào)用的方式,并且協(xié)程只有在IO密集型的任務(wù)中才會(huì)發(fā)揮作用。
協(xié)程只有和異步IO結(jié)合起來(lái)才能發(fā)揮出最大的威力。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
- 上一篇: 使用 cProfile 和火焰图调优 P
- 下一篇: Apache Iceberg 快速入门