簡(jiǎn)單理解異步,非阻塞和 IO 復(fù)用

1.1 同步與異步

同步與異步的理解

同步與異步的重點(diǎn)在消息通知的方式上,也就是調(diào)用結(jié)果通知的方式。
同步: 當(dāng)一個(gè)同步調(diào)用發(fā)出去后,調(diào)用者要一直等待調(diào)用結(jié)果的通知后,才能進(jìn)行后續(xù)的執(zhí)行。
異步:當(dāng)一個(gè)異步調(diào)用發(fā)出去后,調(diào)用者不能立即得到調(diào)用結(jié)果的返回。
異步調(diào)用,要想獲得結(jié)果,一般有兩種方式:

  1. 主動(dòng)輪詢異步調(diào)用的結(jié)果;
  2. 被調(diào)用方通過(guò)callback來(lái)通知調(diào)用方調(diào)用結(jié)果。

生活中的例子

同步買奶茶:小明點(diǎn)單交錢,然后等著拿奶茶;異步買奶茶:小明點(diǎn)單交錢,店員給小明一個(gè)小票,等小明奶茶做好了,再來(lái)取。

異步買奶茶: 小明要想知道奶茶是否做好了,有兩種方式:

  1. 小明主動(dòng)去問(wèn)店員,一會(huì)就去問(wèn)一下:“奶茶做好了嗎?”...直到奶茶做好。這叫輪訓(xùn)。
  2. 等奶茶做好了,店員喊一聲:“小明,奶茶好了!”,然后小明去取奶茶。這叫回調(diào)。

1.2 阻塞與非阻塞

阻塞與非阻塞的理解

阻塞與非阻塞的重點(diǎn)在于進(jìn)/線程等待消息時(shí)候的行為,也就是在等待消息的時(shí)候,當(dāng)前進(jìn)/線程是掛起狀態(tài),還是非掛起狀態(tài)。

阻塞調(diào)用在發(fā)出去后,在消息返回之前,當(dāng)前進(jìn)/線程會(huì)被掛起,直到有消息返回,當(dāng)前進(jìn)/線程才會(huì)被激活.

非阻塞調(diào)用在發(fā)出去后,不會(huì)阻塞當(dāng)前進(jìn)/線程,而會(huì)立即返回。

生活中的例子

阻塞買奶茶:小明點(diǎn)單交錢,干等著拿奶茶,什么事都不做;
非阻塞買奶茶:小明點(diǎn)單交錢,等著拿奶茶,等的過(guò)程中,時(shí)不時(shí)刷刷微博、朋友圈。

1.3 總結(jié)

通過(guò)上面的分析,我們可以得知:

  1. 同步與異步,重點(diǎn)在于消息通知的方式;
  2. 阻塞與非阻塞,重點(diǎn)在于等消息時(shí)候的行為。

所以,就有了下面4種組合方式:

  1. 同步阻塞:小明在柜臺(tái)干等著拿奶茶;
  2. 同步非阻塞:小明在柜臺(tái)邊刷微博邊等著拿奶茶;
  3. 異步阻塞:小明拿著小票啥都不干,一直等著店員通知他拿奶茶;
  4. 異步非阻塞:小明拿著小票,刷著微博,等著店員通知他拿奶茶。

2. IO 復(fù)用

IO 復(fù)用例子說(shuō)明

假設(shè)你是一個(gè)機(jī)場(chǎng)的空管,你需要管理到你機(jī)場(chǎng)的所有的航線, 包括進(jìn)港,出港,有些航班需要放到停機(jī)坪等待,有些航班需要去登機(jī)口接乘客。

你會(huì)怎么做?

最簡(jiǎn)單的做法,就是你去招一大批空管員,然后每人盯一架飛機(jī), 從進(jìn)港,接客,排位,出港,航線監(jiān)控,直至交接給下一個(gè)空港,全程監(jiān)控。

那么問(wèn)題就來(lái)了:

很快你就發(fā)現(xiàn)空管塔里面聚集起來(lái)一大票的空管員,交通稍微繁忙一點(diǎn),新的空管員就已經(jīng)擠不進(jìn)來(lái)了??展軉T之間需要協(xié)調(diào),屋子里面就1,2個(gè)人的時(shí)候還好,幾十號(hào)人以后 ,基本上就成菜市場(chǎng)了。

空管員經(jīng)常需要更新一些公用的東西,比如起飛顯示屏,比如下一個(gè)小時(shí)后的出港排期,最后你會(huì)很驚奇的發(fā)現(xiàn),每個(gè)人的時(shí)間最后都花在了搶這些資源上。

現(xiàn)實(shí)上我們的空管同時(shí)管幾十架飛機(jī)稀松平常的事情:

!(https://upload-images.jianshu.io/upload_images/7616246-d9a948e9eb0f8f31.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

他們?cè)趺醋龅哪??這個(gè)東西叫flight progress strip

每一個(gè)塊代表一個(gè)航班,不同的槽代表不同的狀態(tài),然后一個(gè)空管員可以管理一組這樣的塊(一組航班),而他的工作,就是在航班信息有新的更新的時(shí)候,把對(duì)應(yīng)的塊放到不同的槽子里面。

這個(gè)東西現(xiàn)在還沒(méi)有淘汰哦,只是變成電子的了而已。

是不是覺(jué)得一下子效率高了很多,一個(gè)空管塔里可以調(diào)度的航線可以是前一種方法的幾倍到幾十倍。

如果你把每一個(gè)航線當(dāng)成一個(gè)Sock(I/O 流),空管當(dāng)成你的服務(wù)端Sock管理代碼的話.

第一種方法就是最傳統(tǒng)的多進(jìn)程并發(fā)模型 (每進(jìn)來(lái)一個(gè)新的I/O流會(huì)分配一個(gè)新的進(jìn)程管理。)
第二種方法就是I/O多路復(fù)用 (單個(gè)線程,通過(guò)記錄跟蹤每個(gè)I/O流(sock)的狀態(tài),來(lái)同時(shí)管理多個(gè)I/O流 。)

其實(shí)I/O多路復(fù)用這個(gè)坑爹翻譯可能是這個(gè)概念在中文里面如此難理解的原因。所謂的I/O多路復(fù)用在英文中其實(shí)叫 I/O multiplexing.

重要的事情再說(shuō)一遍: I/O multiplexing 這里面的 multiplexing 指的其實(shí)是在單個(gè)線程通過(guò)記錄跟蹤每一個(gè)Sock(I/O流)的狀態(tài)(對(duì)應(yīng)空管塔里面的Fight progress strip槽)來(lái)同時(shí)管理多個(gè)I/O流. 發(fā)明它的原因,是盡量多的提高服務(wù)器的吞吐能力。

是不是聽起來(lái)好拗口,看個(gè)圖就懂了:

此處輸入圖片的描述

在同一個(gè)線程里面, 通過(guò)撥開關(guān)的方式,來(lái)同時(shí)傳輸多個(gè)I/O流,

最初級(jí)的I/O復(fù)用

所謂的I/O復(fù)用,就是多個(gè)I/O可以復(fù)用一個(gè)進(jìn)程。

采用非阻塞的模式,當(dāng)一個(gè)連接過(guò)來(lái)時(shí),我們不阻塞住,這樣一個(gè)進(jìn)程可以同時(shí)處理多個(gè)連接了。

比如一個(gè)進(jìn)程接受了10000個(gè)連接,這個(gè)進(jìn)程每次從頭到尾的問(wèn)一遍這10000個(gè)連接:“有I/O事件沒(méi)?有的話就交給我處理,沒(méi)有的話我一會(huì)再來(lái)問(wèn)一遍?!?br> 然后進(jìn)程就一直從頭到尾問(wèn)這10000個(gè)連接,如果這1000個(gè)連接都沒(méi)有I/O事件,就會(huì)造成CPU的空轉(zhuǎn),并且效率也很低,不好不好。

升級(jí)版的I/O復(fù)用

上面雖然實(shí)現(xiàn)了基礎(chǔ)版的I/O復(fù)用,但是效率太低了。于是偉大的程序猿們?nèi)账家瓜氲娜ソ鉀Q這個(gè)問(wèn)題...終于!

我們能不能引入一個(gè)代理,這個(gè)代理可以同時(shí)觀察許多I/O流事件呢?

當(dāng)沒(méi)有I/O事件的時(shí)候,這個(gè)進(jìn)程處于阻塞狀態(tài);當(dāng)有I/O事件的時(shí)候,這個(gè)代理就去通知進(jìn)程醒來(lái)?

于是,早期的程序猿們發(fā)明了兩個(gè)代理---selectpoll。

select、poll代理的原理是這樣的:

當(dāng)連接有I/O流事件產(chǎn)生的時(shí)候,就會(huì)去喚醒進(jìn)程去處理。
但是進(jìn)程并不知道是哪個(gè)連接產(chǎn)生的I/O流事件,于是進(jìn)程就挨個(gè)去問(wèn):“請(qǐng)問(wèn)是你有事要處理嗎?”......問(wèn)了99999遍,哦,原來(lái)是第100000個(gè)進(jìn)程有事要處理。那么,前面這99999次就白問(wèn)了,白白浪費(fèi)寶貴的CPU時(shí)間片了!痛哉,惜哉...

  1. select是第一個(gè)實(shí)現(xiàn) (1983 左右在BSD里面實(shí)現(xiàn))
  2. 1997年實(shí)現(xiàn)了poll.
  3. select與poll原理是一樣的,只不過(guò)select只能觀察1024個(gè)連接,poll可以觀察無(wú)限個(gè)連接。

上面看了,select、poll因?yàn)椴恢滥膫€(gè)連接有I/O流事件要處理,性能也挺不好的。

那么,如果發(fā)明一個(gè)代理,每次能夠知道哪個(gè)連接有了I/O流事件,不就可以避免無(wú)意義的空轉(zhuǎn)了嗎?

于是,超級(jí)無(wú)敵、閃閃發(fā)光的epoll,于5年以后, 在2002年被大神 Davide Libenzi 發(fā)明出來(lái)了。

epoll IO多路復(fù)用

epoll代理的原理是這樣的:

當(dāng)連接有I/O流事件產(chǎn)生的時(shí)候,epoll就會(huì)去告訴進(jìn)程哪個(gè)連接有I/O流事件產(chǎn)生,然后進(jìn)程就去處理這個(gè)進(jìn)程。如此,多高效!

epoll 可以說(shuō)是I/O 多路復(fù)用最新的一個(gè)實(shí)現(xiàn),epoll 修復(fù)了poll 和select絕大部分問(wèn)題, 比如:

epoll 現(xiàn)在是線程安全的。
epoll 現(xiàn)在不僅告訴你sock組里面數(shù)據(jù),還會(huì)告訴你具體哪個(gè)sock有數(shù)據(jù),你不用自己去找了。

可是epoll 有個(gè)致命的缺點(diǎn),只有l(wèi)inux支持。于是其他的平臺(tái)實(shí)現(xiàn)類型的多路復(fù)用,比如BSD上面對(duì)應(yīng)的是kqueue, win下對(duì)應(yīng)的iocp。

epoll和select/poll區(qū)別

簡(jiǎn)單說(shuō)epoll和select/poll最大區(qū)別是

  1. epoll內(nèi)部使用了mmap共享了用戶和內(nèi)核的部分空間,避免了數(shù)據(jù)的來(lái)回拷貝
  2. epoll基于事件驅(qū)動(dòng),epoll_ctl注冊(cè)事件并注冊(cè)callback回調(diào)函數(shù),epoll_wait只返回發(fā)生的事件避免了像select和poll對(duì)事件的整個(gè)輪尋操作。

3. Nginx 異步,非阻塞,IO多路復(fù)用

Nginx 這樣出眾,正是他采用了異步,非阻塞,IO多路復(fù)用。

Nginx之前是單進(jìn)程的??聪滤倪M(jìn)程。1個(gè)master進(jìn)程,2個(gè)work進(jìn)程。

  1. $ pstree |grep nginx
  2. |-+= 81666 root nginx: master process nginx
  3. | |--- 82500 nobody nginx: worker process
  4. | \--- 82501 nobody nginx: worker process

每進(jìn)來(lái)一個(gè)request,會(huì)有一個(gè)worker進(jìn)程去處理。但不是全程的處理,處理到什么程度呢?處理到可能發(fā)生阻塞的地方,比如向上游(后端)服務(wù)器轉(zhuǎn)發(fā)request,并等待請(qǐng)求返回。那么,這個(gè)處理的worker不會(huì)這么傻等著,他會(huì)在發(fā)送完請(qǐng)求后,注冊(cè)一個(gè)事件:“如果upstream返回了,告訴我一聲,我再接著干”。于是他就休息去了。這就是異步。此時(shí),如果再有request 進(jìn)來(lái),他就可以很快再按這種方式處理。這就是非阻塞IO多路復(fù)用。而一旦上游服務(wù)器返回了,就會(huì)觸發(fā)這個(gè)事件,worker才會(huì)來(lái)接手,這個(gè)request才會(huì)接著往下走。這就是異步回調(diào)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容