同步和異步關注的是消息通訊機制 (synchronous communication/ asynchronous communication)。所謂同步,就是在發出一個*調用*時,在沒有獲得結果以前,該*調用*就不返回。可是一旦調用返回,就獲得返回值了。換句話說,就是由*調用者*主動等待這個*調用*的結果。而異步則是相反,*調用*在發出以後,這個調用就直接返回了,因此沒有返回結果。換句話說,當一個異步過程調用發出後,調用者不會馬上獲得結果。而是在*調用*發出後,*被調用者*經過狀態、通知來通知調用者,或經過回調函數處理這個調用。php
典型的異步編程模型好比Node.jshtml
舉個通俗的例子:你打電話問書店老闆有沒有《分佈式系統》這本書,若是是同步通訊機制,書店老闆會說,你稍等,"我查一下",而後開始查啊查,等查好了(多是5秒,也多是一天)告訴你結果(返回結果)。而異步通訊機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,而後直接掛電話了(不返回結果)。而後查好了,他會主動打電話給你。在這裏老闆經過"回電"這種方式來回調。前端
阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.阻塞調用是指調用結果返回以前,當前線程會被掛起。調用線程只有在獲得結果以後纔會返回。非阻塞調用指在不能馬上獲得結果以前,該調用不會阻塞當前線程。nginx
仍是上面的例子,你打電話問書店老闆有沒有《分佈式系統》這本書,你若是是阻塞式調用,你會一直把本身"掛起",直到獲得這本書有沒有的結果,若是是非阻塞式調用,你無論老闆有沒有告訴你,你本身先一邊去玩了, 固然你也要偶爾過幾分鐘check一下老闆有沒有返回結果。在這裏阻塞與非阻塞與是否同步異步無關。跟老闆經過什麼方式回答你結果無關。web
因爲進程是不可直接訪問外部設備的,因此只能調用內核去調用外部的設備(上下文切換),而後外部設備好比磁盤,讀出存儲在設備自身的數據傳送給內核緩衝區,內核緩衝區在copy數據到用戶進程的緩衝區。在外部設備響應的給到用戶進程過程當中,包含了兩個階段;因爲數據響應方式的不一樣,因此就有了不一樣的I/O模型。apache
通常有五種I/O模型:編程
阻塞式I/O模型:後端
默認狀況下,全部套接字都是阻塞的。進程掛起,內核等待外部IO響應,IO完成傳送數據到kernel buffer,數據再從buffer複製到用戶的進程空間緩存
非阻塞式I/O:服務器
在內核請求IO設備響應指令發出後,數據就開始準備,在此期間用戶進程沒有阻塞,也就是沒有掛起,它一值在詢問或者check數據有沒有傳送到kernel buffer中,忙等…。可是第二個階段(數據從kernel buffer複製到用戶進程空間)依然是阻塞的。但這種IO模型會大量的佔用CPU的時間,效率很低效,不多使用。
I/O多路複用(select,poll,epoll...):
在內核請求IO設備響應指令發出後,數據就開始準備,在此期間用戶進程是阻塞的。數據從kernel buffer複製到用戶進程的過程也是阻塞的。可是和阻塞I/O所不一樣的是,它能夠同時阻塞多個I/O操做,並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫時,才真正調用I/O操做函數,也就是說一個線程能夠響應多個請求。
信號驅動式I/O(事件驅動)
第一階段是非阻塞的,當數據傳送的kernel buffer後,直接用信號的方式通知線程,用的很少
異步I/O:
在整個操做(包括將數據從內核拷貝到用戶空間)完成後才通知用戶進程
整個的彙總
這三種模式都是屬於IO複用模型。
select,poll是主動查詢,它們能夠同時查詢多個fd(文件句柄)的狀態,另外select有fd個數的限制,poll沒有限制。select和poll不一樣的是,他們建立的事件描述符不一樣,select建立讀、寫、異常三個集合,而poll在一個集合內設定三種描述,因爲select和poll每一個循環都會檢查事件的發生,而poll的事件比較少,性能上比select要好一些;
epoll是基於回調函數的,無輪詢。若是當套接字比較多的時候,每次select()都要經過遍歷FD_SETSIZE個Socket來完成調度,無論哪一個Socket是活躍的,都遍歷一遍。這會浪費不少CPU時間。若是能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操做,那就避免了輪詢,這正是epoll(Linux)、kqueue(FreeBSD)、/dev/poll(soloris)作的。舉個經典例子,假設你在大學讀書,住的宿舍樓有不少間房間,你的朋友要來找你。select版宿管大媽就會帶着你的朋友挨個房間去找,直到找到你爲止。而epoll版宿管大媽會先記下每位同窗的房間號,你的朋友來時,只需告訴你的朋友你住在哪一個房間便可,不用親自帶着你的朋友滿大樓找人。若是來了10000我的,都要找本身住這棟樓的同窗時,select版和epoll版宿管大媽,誰的效率更高,不言自明。同理,在高併發服務器中,輪詢I/O是最耗時間的操做之一,select、epoll、/dev/poll的性能誰的性能更高,一樣十分明瞭。
因爲web服務器是一對多的關係,一般完成並行處理的方式有多進程、多線程、異步三種方式。
多進程:多進程就是每一個進程對應一個鏈接來處理請求,進程獨立響應本身的請求,一個進程掛了,並不會影響到其餘的請求;並且設計簡單,不會產生內存泄漏等問題,所以進程比較穩定。可是進程在建立的時候通常是fork機制,會存在內存複製的問題,另外在高併發的狀況下,上下文切換將很頻繁,這樣將消耗不少的性能和時間。早期的apache使用的prework模型就多進程方式,可是apache會預先建立幾個進程,等待用戶的響應,請求完畢,進程也不會結束。所以性能上有優化不少。
多線程:每一個線程響應一個請求,因爲線程之間共享進程的數據,因此線程的開銷較小,性能就會提升。因爲線程管理須要程序本身申請和釋放內存,因此當存在內存等問題時,可能會運行很長時間纔會暴露問題,因此在必定程度上還不是很穩定。apache的worker模式就是這種方式
異步的方式:nginx的epoll,apache的event也支持,很少說了
Nginx的IO模型是基於事件驅動的,使得應用程序在多個IO句柄間快速切換,實現所謂的異步IO。事件驅動服務器,最適合作的就是IO密集型工做,如反向代理,它在客戶端與WEB服務器之間起一個數據中轉做用,純粹是IO操做,自身並不涉及到複雜計算。反向代理用事件驅動來作,顯然更好,一個工做進程就能夠run了,沒有進程、線程管理的開銷,CPU、內存消耗都小。
Apache這類應用服務器,通常要跑具體的業務應用,如科學計算、圖形圖像等。它們極可能是CPU密集型的服務,事件驅動並不合適。例如一個計算耗時2秒,那麼這2秒就是徹底阻塞的,什麼event都沒用。想一想MySQL若是改爲事件驅動會怎麼樣,一個大型的join或sort就會阻塞住全部客戶端。這個時候多進程或線程就體現出優點,每一個進程各幹各的事,互不阻塞和干擾。固然,現代CPU愈來愈快,單個計算阻塞的時間可能很小,但只要有阻塞,事件編程就毫無優點。因此進程、線程這類技術,並不會消失,而是與事件機制相輔相成,長期存在。
總的說來,事件驅動適合於IO密集型服務,多進程或線程適合於CPU密集型服務
其實也就是說nginx比較適合作前端代理,或者處理靜態文件(尤爲高併發狀況下),而apache適合作後端的應用服務器,功能強大[php, rewrite…],穩定性高。