1.1 同步與異步
同步與異步的理解
同步與異步的重點在消息通知的方式上顺献,也就是調(diào)用結(jié)果通知的方式旗国。
同步
: 當(dāng)一個同步調(diào)用發(fā)出去后,調(diào)用者要一直等待調(diào)用結(jié)果的通知后注整,才能進(jìn)行后續(xù)的執(zhí)行能曾。
異步
:當(dāng)一個異步調(diào)用發(fā)出去后嫁怀,調(diào)用者不能立即得到調(diào)用結(jié)果的返回。
異步調(diào)用借浊,要想獲得結(jié)果塘淑,一般有兩種方式:
- 主動輪詢異步調(diào)用的結(jié)果;
- 被調(diào)用方通過callback來通知調(diào)用方調(diào)用結(jié)果。
生活中的例子
同步買奶茶
:小明點單交錢蚂斤,然后等著拿奶茶存捺;異步買奶茶:小明點單交錢,店員給小明一個小票曙蒸,等小明奶茶做好了捌治,再來取。
異步買奶茶
: 小明要想知道奶茶是否做好了纽窟,有兩種方式:
- 小明主動去問店員肖油,一會就去問一下:“奶茶做好了嗎?”...直到奶茶做好臂港。這叫輪訓(xùn)森枪。
- 等奶茶做好了,店員喊一聲:“小明审孽,奶茶好了县袱!”,然后小明去取奶茶佑力。這叫回調(diào)式散。
1.2 阻塞與非阻塞
阻塞與非阻塞的理解
阻塞與非阻塞的重點在于進(jìn)/線程等待消息時候的行為,也就是在等待消息的時候打颤,當(dāng)前進(jìn)/線程是掛起狀態(tài)暴拄,還是非掛起狀態(tài)。
阻塞調(diào)用
在發(fā)出去后编饺,在消息返回之前乖篷,當(dāng)前進(jìn)/線程會被掛起,直到有消息返回反肋,當(dāng)前進(jìn)/線程才會被激活.
非阻塞
調(diào)用在發(fā)出去后那伐,不會阻塞當(dāng)前進(jìn)/線程,而會立即返回石蔗。
生活中的例子
阻塞買奶茶
:小明點單交錢罕邀,干等著拿奶茶,什么事都不做养距;
非阻塞買奶茶
:小明點單交錢诉探,等著拿奶茶,等的過程中棍厌,時不時刷刷微博肾胯、朋友圈竖席。
1.3 總結(jié)
通過上面的分析,我們可以得知:
- 同步與異步敬肚,重點在于消息通知的方式;
- 阻塞與非阻塞毕荐,重點在于等消息時候的行為。
所以艳馒,就有了下面4種組合方式:
- 同步阻塞:小明在柜臺干等著拿奶茶憎亚;
- 同步非阻塞:小明在柜臺邊刷微博邊等著拿奶茶;
- 異步阻塞:小明拿著小票啥都不干弄慰,一直等著店員通知他拿奶茶第美;
- 異步非阻塞:小明拿著小票,刷著微博陆爽,等著店員通知他拿奶茶什往。
2. IO 復(fù)用
IO 復(fù)用例子說明
假設(shè)你是一個機(jī)場的空管,你需要管理到你機(jī)場的所有的航線慌闭, 包括進(jìn)港别威,出港,有些航班需要放到停機(jī)坪等待贡必,有些航班需要去登機(jī)口接乘客兔港。
你會怎么做?
最簡單的做法庸毫,就是你去招一大批空管員仔拟,然后每人盯一架飛機(jī), 從進(jìn)港飒赃,接客利花,排位,出港礁芦,航線監(jiān)控闭翩,直至交接給下一個空港颗管,全程監(jiān)控。
那么問題就來了:
很快你就發(fā)現(xiàn)空管塔里面聚集起來一大票的空管員挠乳,交通稍微繁忙一點,新的空管員就已經(jīng)擠不進(jìn)來了姑躲∷铮空管員之間需要協(xié)調(diào),屋子里面就1,2個人的時候還好黍析,幾十號人以后 卖怜,基本上就成菜市場了。
空管員經(jīng)常需要更新一些公用的東西阐枣,比如起飛顯示屏马靠,比如下一個小時后的出港排期奄抽,最后你會很驚奇的發(fā)現(xiàn),每個人的時間最后都花在了搶這些資源上甩鳄。
現(xiàn)實上我們的空管同時管幾十架飛機(jī)稀松平常的事情:
他們怎么做的呢逞度?這個東西叫flight progress strip
。
每一個塊代表一個航班妙啃,不同的槽代表不同的狀態(tài)第晰,然后一個空管員可以管理一組這樣的塊(一組航班),而他的工作彬祖,就是在航班信息有新的更新的時候茁瘦,把對應(yīng)的塊放到不同的槽子里面。
這個東西現(xiàn)在還沒有淘汰哦储笑,只是變成電子的了而已甜熔。
是不是覺得一下子效率高了很多,一個空管塔里可以調(diào)度的航線可以是前一種方法的幾倍到幾十倍突倍。
如果你把每一個航線當(dāng)成一個Sock(I/O 流),空管當(dāng)成你的服務(wù)端Sock管理代碼的話.
第一種方法就是最傳統(tǒng)的多進(jìn)程并發(fā)模型 (每進(jìn)來一個新的I/O流會分配一個新的進(jìn)程管理腔稀。)
第二種方法就是I/O多路復(fù)用 (單個線程,通過記錄跟蹤每個I/O流(sock)的狀態(tài)羽历,來同時管理多個I/O流 焊虏。)
其實I/O多路復(fù)用
這個坑爹翻譯可能是這個概念在中文里面如此難理解的原因。所謂的I/O多路復(fù)用在英文中其實叫 I/O multiplexing.
重要的事情再說一遍: I/O multiplexing 這里面的 multiplexing 指的其實是在單個線程通過記錄跟蹤每一個Sock(I/O流)的狀態(tài)(對應(yīng)空管塔里面的Fight progress strip槽)來同時管理多個I/O流. 發(fā)明它的原因秕磷,是盡量多的提高服務(wù)器的吞吐能力诵闭。
是不是聽起來好拗口,看個圖就懂了:
在同一個線程里面澎嚣, 通過撥開關(guān)的方式疏尿,來同時傳輸多個I/O流,
最初級的I/O復(fù)用
所謂的I/O復(fù)用易桃,就是多個I/O可以復(fù)用一個進(jìn)程褥琐。
采用非阻塞的模式,當(dāng)一個連接過來時晤郑,我們不阻塞住敌呈,這樣一個進(jìn)程可以同時處理多個連接了。
比如一個進(jìn)程接受了10000個連接造寝,這個進(jìn)程每次從頭到尾的問一遍這10000個連接:“有I/O事件沒磕洪?有的話就交給我處理,沒有的話我一會再來問一遍匹舞『峙福”
然后進(jìn)程就一直從頭到尾問這10000個連接,如果這1000個連接都沒有I/O事件赐稽,就會造成CPU的空轉(zhuǎn)叫榕,并且效率也很低浑侥,不好不好。
升級版的I/O復(fù)用
上面雖然實現(xiàn)了基礎(chǔ)版的I/O復(fù)用晰绎,但是效率太低了寓落。于是偉大的程序猿們?nèi)账家瓜氲娜ソ鉀Q這個問題...終于!
我們能不能引入一個代理荞下,這個代理可以同時觀察許多I/O流事件呢伶选?
當(dāng)沒有I/O事件的時候,這個進(jìn)程處于阻塞狀態(tài)尖昏;當(dāng)有I/O事件的時候仰税,這個代理就去通知進(jìn)程醒來?
于是抽诉,早期的程序猿們發(fā)明了兩個代理---select
陨簇、poll
。
select迹淌、poll代理的原理是這樣的:
當(dāng)連接有I/O流事件產(chǎn)生的時候河绽,就會去喚醒進(jìn)程去處理。
但是進(jìn)程并不知道是哪個連接產(chǎn)生的I/O流事件唉窃,于是進(jìn)程就挨個去問:“請問是你有事要處理嗎耙饰?”......問了99999遍,哦纹份,原來是第100000個進(jìn)程有事要處理苟跪。那么,前面這99999次就白問了矮嫉,白白浪費寶貴的CPU時間片了削咆!痛哉,惜哉...
- select是第一個實現(xiàn) (1983 左右在BSD里面實現(xiàn))
- 1997年實現(xiàn)了poll.
- select與poll原理是一樣的蠢笋,只不過select只能觀察1024個連接,poll可以觀察無限個連接鳞陨。
上面看了昨寞,select、poll因為不知道哪個連接有I/O流事件要處理厦滤,性能也挺不好的援岩。
那么,如果發(fā)明一個代理掏导,每次能夠知道哪個連接有了I/O流事件享怀,不就可以避免無意義的空轉(zhuǎn)了嗎?
于是趟咆,超級無敵添瓷、閃閃發(fā)光的epoll梅屉,于5年以后, 在2002年被大神 Davide Libenzi 發(fā)明出來了。
epoll IO多路復(fù)用
epoll代理的原理是這樣的:
當(dāng)連接有I/O流事件產(chǎn)生的時候鳞贷,epoll就會去告訴進(jìn)程哪個連接有I/O流事件產(chǎn)生坯汤,然后進(jìn)程就去處理這個進(jìn)程。如此搀愧,多高效惰聂!
epoll 可以說是I/O 多路復(fù)用最新的一個實現(xiàn),epoll 修復(fù)了poll 和select絕大部分問題, 比如:
epoll 現(xiàn)在是線程安全的咱筛。
epoll 現(xiàn)在不僅告訴你sock組里面數(shù)據(jù)搓幌,還會告訴你具體哪個sock有數(shù)據(jù),你不用自己去找了迅箩。
可是epoll 有個致命的缺點鼻种,只有l(wèi)inux支持。于是其他的平臺實現(xiàn)類型的多路復(fù)用沙热,比如BSD上面對應(yīng)的是kqueue
叉钥, win下對應(yīng)的iocp
。
epoll和select/poll區(qū)別
簡單說epoll和select/poll最大區(qū)別是
- epoll內(nèi)部使用了mmap共享了用戶和內(nèi)核的部分空間篙贸,避免了數(shù)據(jù)的來回拷貝
- epoll基于事件驅(qū)動投队,epoll_ctl注冊事件并注冊callback回調(diào)函數(shù),epoll_wait只返回發(fā)生的事件避免了像select和poll對事件的整個輪尋操作爵川。
3. Nginx 異步敷鸦,非阻塞,IO多路復(fù)用
Nginx 這樣出眾寝贡,正是他采用了異步扒披,非阻塞,IO多路復(fù)用圃泡。
Nginx之前是單進(jìn)程的碟案。看下他的進(jìn)程颇蜡。1個master進(jìn)程价说,2個work進(jìn)程。
$ pstree |grep nginx
|-+= 81666 root nginx: master process nginx
| |--- 82500 nobody nginx: worker process
| \--- 82501 nobody nginx: worker process
每進(jìn)來一個request风秤,會有一個worker進(jìn)程去處理鳖目。但不是全程的處理,處理到什么程度呢缤弦?處理到可能發(fā)生阻塞的地方领迈,比如向上游(后端)服務(wù)器轉(zhuǎn)發(fā)request,并等待請求返回。那么狸捅,這個處理的worker不會這么傻等著衷蜓,他會在發(fā)送完請求后,注冊一個事件:“如果upstream返回了薪贫,告訴我一聲恍箭,我再接著干”。于是他就休息去了瞧省。這就是異步
扯夭。此時,如果再有request 進(jìn)來鞍匾,他就可以很快再按這種方式處理交洗。這就是非阻塞
和IO多路復(fù)用
。而一旦上游服務(wù)器返回了橡淑,就會觸發(fā)這個事件构拳,worker才會來接手,這個request才會接著往下走梁棠。這就是異步回調(diào)
置森。