最近在招聘中,聊到基礎的網絡編程的時候,發現很多人對BIO、NIO、AIO等理解很是模糊,以爲有必要寫文章來糾正下不少人的誤解。
在談這些以前,很是有必要先介紹下Unix 5種IO模型:
阻塞:
阻塞是最經常使用的IO模型,默認狀況下全部的文件操做都是阻塞的。以套接字編程爲例。在進程空間中調用recvfrom,其系統調用直到數據報文到達且被拷貝到應用程序進程的緩存區(或者發生錯誤)後才返回,期間一直在等待。進程在從調用recvfrom開始到它返回的整段時間內是被阻塞的。有一張很經典的圖:
非阻塞:
調用recvfrom從應用層到內核的過程當中,若是該緩衝區沒有數據的話,則直接返回一個EWOULDBLOCK的錯誤,通常會輪詢的進行檢查狀態,看內核空間有沒有數據來。直到有數據,最後完成拷貝。以下圖:
IO多路複用:
Linux系統提供的select/poll/epoll,進程將一個或者多個FD(文件描述符)傳遞給一個或者多個poll/select系統調用,阻塞在select。select和poll能夠幫助偵聽不少的FD是否準備就緒。可是,select和poll是順序掃描去檢查FD的就緒狀態,效率比較低,並且支持的FD數量有限(沒記錯的話,默認好像是1024仍是2048,具體記不清)。而epoll是經過事件驅動的方式,當有FD準備就緒的時候,當即回調函數rollback。如圖:
談到epoll,不得不提一個經典的問題,apache和nginx的對比,爲何nginx比apache效率高不少,這就是根本的緣由。
信號驅動:
這種模型在實際應用的很是少,這裏不作過多介紹,能夠看圖:
異步:
告知內核啓動某個操做,並讓內核在整個操做完成後(包括將數據從內核空間拷貝到本身的緩衝區)通知。異步IO的主要特色是完成操做後主動通知。如圖:
nginx
好,上面的可能有點抽象。下面用通俗點的語言來總結一下阻塞,非阻塞,同步,異步。
阻塞,非阻塞:進程/線程要訪問的數據是否就緒,進程/線程是否須要等待;
同步,異步:訪問數據的方式,同步須要主動讀寫數據,在讀寫數據的過程當中仍是會阻塞;
異步只須要I/O操做完成的通知,並不主動讀寫數據,由操做系統內核完成數據的讀寫。
再舉個網上流傳的,很是容易理解的例子:
老張愛喝茶,廢話不說,煮開水。
出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。
1 老張把水壺放到火上,立等水開。(同步阻塞)老張以爲本身有點傻
2 老張把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞)
老張仍是以爲本身有點傻,因而變高端了,買了把會響笛的那種水壺。水開以後,能大聲發出嘀~~~~的噪音。
3 老張把響水壺放到火上,立等水開。(異步阻塞)老張以爲這樣傻等意義不大
4 老張把響水壺放到火上,去客廳看電視,水壺響以前再也不去看它了,響了再去拿壺。(異步非阻塞)老張以爲本身聰明瞭。
所謂同步異步,只是對於水壺而言。普通水壺,同步;響水壺,異步。雖然都能幹活,但響水壺能夠在本身完工以後,提示老張水開了。這是普通水壺所不能及的。
同步只能讓調用者去輪詢本身(狀況2中),形成老張效率的低下。所謂阻塞非阻塞,僅僅對於老張而言。立等的老張,
阻塞;看電視的老張,非阻塞。狀況1和狀況3中老張就是阻塞的,媳婦喊他都不知道。
雖然3中響水壺是異步的,可對於立等的老張沒有太大的意義。因此通常異步是配合非阻塞使用的,這樣才能發揮異步的效用。apache