什么是协程?
這幾天一直在看協程相關的介紹,自動駕駛系統Apollo中使用了協程作為底層調度單元,那么協程究竟是如何工作的呢?通過本文你可以了解到,為什么需要協程?以及使用協程有哪些注意事項?
為什么需要協程?
我們都知道多線程,當需要同時執行多項任務的時候,就會采用多線程并發執行。拿手機支付舉例子,當收到付款信息的時候,需要查詢數據庫來判斷余額是否充足,然后再進行付款。
假設最開始我們只有可憐的10個用戶,收到10條付款消息之后,我們開啟啟動10個線程去查詢數據庫,由于用戶量很少,結果馬上就返回了。第2天用戶增加到了100人,你選擇增加100個線程去查詢數據庫,等到第三天,你們加大了優惠力度,這時候有1000人同時在線付款,你按照之前的方法,繼續采用1000個線程去查詢數據庫,并且隱隱覺察到有什么不對。
不斷增長的線程
幾天之后,見勢頭大好,運營部門開始不停的補貼消費券,展開了史無前例的大促銷,你們的用戶開始爆炸增長,這時候有10000人同時在線付款,你打算啟動10000個線程來處理任務。等等,問題來了,因為每個線程至少會占用4M的內存空間,10000個線程會消耗39G的內存,而服務器的內存配置只有區區8G,這時候你有2種選擇,一是選擇增加服務器,二是選擇提高代碼效率。那么是否有方法能夠提高效率呢?
我們知道操作系統在線程等待IO的時候,會阻塞當前線程,切換到其它線程,這樣在當前線程等待IO的過程中,其它線程可以繼續執行。當系統線程較少的時候沒有什么問題,但是當線程數量非常多的時候,卻產生了問題。一是系統線程會占用非常多的內存空間,二是過多的線程切換會占用大量的系統時間。
線程切換
協程剛好可以解決上述2個問題。協程運行在線程之上,當一個協程執行完成后,可以選擇主動讓出,讓另一個協程運行在當前線程之上。協程并沒有增加線程數量,只是在線程的基礎之上通過分時復用的方式運行多個協程,而且協程的切換在用戶態完成,切換的代價比線程從用戶態到內核態的代價小很多。
協程切換
回到上面的問題,我們只需要啟動100個線程,每個線程上運行100個協程,這樣不僅減少了線程切換開銷,而且還能夠同時處理10000個讀取數據庫的任務,很好的解決了上述任務。
知道了協程的工作方式,那么我們再看下使用協程有哪些注意事項。
協程的注意事項
實際上協程并不是什么銀彈,協程只有在等待IO的過程中才能重復利用線程,上面我們已經講過了,線程在等待IO的過程中會陷入阻塞狀態,意識到問題沒有?
假設協程運行在線程之上,并且協程調用了一個阻塞IO操作,這時候會發生什么?實際上操作系統并不知道協程的存在,它只知道線程,因此在協程調用阻塞IO操作的時候,操作系統會讓線程進入阻塞狀態,當前的協程和其它綁定在該線程之上的協程都會陷入阻塞而得不到調度,這往往是不能接受的。
因此在協程中不能調用導致線程阻塞的操作。也就是說,協程只有和異步IO結合起來,才能發揮最大的威力。
那么如何處理在協程中調用阻塞IO的操作呢?一般有2種處理方式:
協程對計算密集型的任務也沒有太大的好處,計算密集型的任務本身不需要大量的線程切換,因此協程的作用也十分有限,反而還增加了協程切換的開銷。
以上就是協程的注意事項。這里順帶一提JavaScript的異步變同步的調用方式,如果協程能夠實現該類型的語法,不僅可以把異步操作變為同步,同時在IO操作的時候還能夠不占用CPU,寫起來非常方便。
異步變同步的調用方式只是一種編程方式,不管是用線程還是用協程都可以實現這種編程方式,好處是不用在處理非常多的回調。
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;} }總結
在有大量IO操作業務的情況下,我們采用協程替換線程,可以到達很好的效果,一是降低了系統內存,二是減少了系統切換開銷,因此系統的性能也會提升。
在協程中盡量不要調用阻塞IO的方法,比如打印,讀取文件,Socket接口等,除非改為異步調用的方式,并且協程只有在IO密集型的任務中才會發揮作用。
協程只有和異步IO結合起來才能發揮出最大的威力。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: 使用 cProfile 和火焰图调优 P
- 下一篇: Java 异步编程:从 Future 到