大家對進程和線程都很熟悉,但是對于纖程(或是協(xié)程)感到陌生。
其實協(xié)程在很早的時候就已經(jīng)開始使用了。
最開始使用進程,發(fā)現(xiàn)對內(nèi)存資源的消耗過大,于是采用了一個進程里面跑多個線程,這樣減小了系統(tǒng)內(nèi)存的壓力(c10k問題),然而還是頂不住需求的不斷增大。select基于事件驅動便應運而生,select是基于輪尋策略的事件處理方式,在select里注冊事件后,select輪詢所有的事件,把可讀,可寫,有異常的事件返回。這樣大大的提高了效率。之后為了提高select的性能,epoll出現(xiàn)了(之間有poll增加了select1024個端口監(jiān)測的限制)。
epoll采用了在內(nèi)核中注冊回調函數(shù),當有讀寫請求的時候,回調函數(shù)會把請求對應的socket寫在對應的鏈表里面。遍歷鏈表就可以獲取到整個io請求,最后會在紅黑樹上查找是否有注冊的這個請求。
基于事件驅動效率很高,但是對于開發(fā)人員并不太友好(想想在回調函數(shù)中嵌套再嵌套的場景吧-,-哈哈)。這里借用知乎的一個例子
鏈接:https://www.zhihu.com/question/32218874/answer/56255707
假設你有10個文件,你想把10個文件拼接起來模擬成一個大文件。但是你內(nèi)存又沒那么大,那只好讀一點讓用戶消耗一點了。所以你得寫個filecat這樣的adaptor。
如果所有接口都用同步,用戶是爽了,仿佛直接在讀一個文件;你實現(xiàn)得很蛋疼,每當用戶問你要數(shù)據(jù)的時候,你得先檢查我現(xiàn)在讀到第幾個文件了,讀完了沒有,后面還有沒有別的文件了,要維護一坨狀態(tài)。
如果所有接口都用異步,你是爽了,直接把用戶的回調挨個傳到不同的文件讀寫調用里去;用戶則蛋疼了。要是用戶想讀三行,停一停去做些別的(比如處理下這三行),是很麻煩的,因為你這個adaptor往用戶端塞數(shù)據(jù)塞得根本停不下來,用戶有什么想法都得往回調函數(shù)里塞。“我現(xiàn)在讀到第幾行了?如果小于三行,就先送到處理頭三行的函數(shù)里去;如果等于三行,就要拿一下處理好的三行并送去不知道什么地方,如果大于三行,再干點別的。。。”所以用戶得手動維護這些狀態(tài)。
你會發(fā)現(xiàn)無論是同步還是異步,都有這種“手動維護狀態(tài)”的問題。要想讓adaptor和用戶都開心,解法自然是協(xié)程。我現(xiàn)在就用代碼來表示狀態(tài),執(zhí)行一行就是狀態(tài)的轉移。但是兩頭的狀態(tài)要交替變化,這邊做三行,輪到那邊做了;那邊做完了,又回到這邊來,這跳來跳去的,就是協(xié)程了
然而纖程并不是銀彈,是在損失了少量的性能的情況下做出的妥協(xié)。(話說紅黑樹不也是妥協(xié)的結果么-,-0)cpu負載增加,內(nèi)存跑熱,提升了io吞吐量。