同步, 就是要本身去輪詢狀態好了沒有linux
異步, 就是會有信號通知你web
阻塞, 就是當前線程什麼活也不能幹緩存
非阻塞, 就是當前線程先去幹其餘的網絡
網絡IO的本質:多線程
阻塞IO模型:併發
# 1.進程運行, 而後經過recvfrom進行系統調用, 至關於調用了內核中的一個函數異步
# 2.系統從運行態轉到內核態, 完成系統調用的相關操做, 此時本來的應用程序處於阻塞態(這個阻塞態是應用程序本身進行阻塞的)socket
# 3.系統從內核態回到運行態, 應用程序便從阻塞態進入就緒態async
非阻塞IO模型:函數
就是輪詢, 發出系統調用, 此時網絡數據沒有徹底寫滿到內核緩衝區的時候, 系統調用返回一個錯誤的狀態碼, 而後進程該幹嗎幹嗎.
以後不斷重複上面這個動做, 知道內核緩衝去寫滿了, 系統調用返回一個正確的狀態碼
IO多路複用:
上面兩個是內核和進程, 這裏多加了兩個進程一塊兒玩, 一個是select, 一個是poll
1. 進程調用select, 而不是去進行系統調用
2. select只管看內核緩衝區裏網絡數據是否是寫滿了, 經過非阻塞輪詢, 寫滿了以後, select就返回一個可讀的狀態碼, 而後由select進行recvfrom的系統調用
故事:老王燒開水。
出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。
老王想了想,有好幾種等待方式
1.老王用水壺煮水,而且站在那裏,無論水開沒開,每隔必定時間看看水開了沒。-同步阻塞
老王想了想,這種方法不夠聰明。
2.老王仍是用水壺煮水,再也不傻傻的站在那裏看水開,跑去寢室上網,可是仍是會每隔一段時間過來看看水開了沒有,水沒有開就走人。-同步非阻塞
老王想了想,如今的方法聰明瞭些,可是仍是不夠好。
3.老王此次使用高大上的響水壺來煮水,站在那裏,可是不會再每隔一段時間去看水開,而是等水開了,水壺會自動的通知他。-異步阻塞
老王想了想,不會呀,既然水壺能夠通知我,那我爲何還要傻傻的站在那裏等呢,嗯,得換個方法。
4.老王仍是使用響水壺煮水,跑到客廳上網去,等着響水壺本身把水煮熟了之後通知他。-異步非阻塞
老王豁然,這下感受輕鬆了不少。
同步和異步
同步就是燒開水,須要本身去輪詢(每隔一段時間去看看水開了沒),異步就是水開了,而後水壺會通知你水已經開了,你能夠回來處理這些開水了。
同步和異步是相對於操做結果來講,會不會等待結果返回。
阻塞和非阻塞
阻塞就是說在煮水的過程當中,你不能夠去幹其餘的事情,非阻塞就是在一樣的狀況下,能夠同時去幹其餘的事情。阻塞和非阻塞是相對於線程是否被阻塞。
其實,這二者存在本質的區別,它們的修飾對象是不一樣的。阻塞和非阻塞是指進程訪問的數據若是還沒有就緒,進程是否須要等待,簡單說這至關於函數內部的實現區別,也就是未就緒時是直接返回仍是等待就緒。
而同步和異步是指訪問數據的機制,同步通常指主動請求並等待I/O操做完畢的方式,當數據就緒後在讀寫的時候必須阻塞,異步則指主動請求數據後即可以繼續處理其它任務,隨後等待I/O,操做完畢的通知,這可使進程在數據讀寫時也不阻塞。
網絡IO的模型大體包括下面幾種
網絡IO的本質是socket的讀取,socket在linux系統被抽象爲流,IO能夠理解爲對流的操做。對於一次IO訪問,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間,因此通常會經歷兩個階段:
對於socket流而言:
這也是最經常使用的模型,默認狀況下全部的套接字都是 阻塞
的;
咱們把recvfrom函數視爲系統調用,由於咱們正區分進程和內核,系統調用通常都會從在應用進程空間中運行切換到內核空間中運行,一段時間後又再切換回來;
# 1.進程運行, 而後經過recvfrom進行系統調用, 至關於調用了內核中的一個函數
# 2.系統從運行態轉到內核態, 完成系統調用的相關操做, 此時本來的應用程序處於阻塞態(這個阻塞態是應用程序本身進行阻塞的)
# 3.系統從內核態回到運行態, 應用程序便從阻塞態進入就緒態
應用進程從 進行系統調用
到 複製數據報到應用進程的緩衝區完成
的整段時間內是被阻塞的;在這個過程當中,要麼正確到達,要麼系統調用被信號打斷;直到數據報被複制到用戶進程完成後,用戶進程才解除阻塞的狀態,固然,這是用戶進程本身進行的阻塞;
非阻塞,當所請求的I/O操做非得把當前進程設置成睡眠才能完成時,不要把當前進程設置成睡眠,而是返回一個錯誤信息(數據報沒有準備好的狀況下),此時當前進程能夠作其它的事情,不用阻塞;
前三次系統調用時都沒有數據能夠返回,內核均返回一個 EWOULDBLOCK
,而且不會阻塞當前進程,直到第四次詢問內核緩衝區是否有數據的時候,此時內核緩衝區中已經有一個準備好的數據,所以將內核數據複製到用戶空間,此時系統調用則返回成功;
當一個應用進程像這樣對一個非阻塞socket循環調用
recv/recvfrom
時,則稱爲輪詢;應用進程持續輪詢內核,以查看某個操做是否就緒,這麼作每每消耗大量的CPU時間。
優勢:相較於阻塞模型,非阻塞不用再等待任務,而是把時間花費到其它任務上,也就是這個當前線程同時處理多個任務;
缺點:致使任務完成的響應延遲增大了,由於每隔一段時間纔去執行詢問的動做,可是任務可能在兩個詢問動做的時間間隔內完成,這會致使總體數據吞吐量的下降。
有了I/O複用,咱們就能夠調用 select或poll
,讓其阻塞在兩個系統調用(1.詢問數據是否準備好而且直到數據準備好才返回;2.內核是否把數據所有複製完成到用戶進程)中的某一個之上
阻塞於 select
調用,等待數據報套接字變爲可讀。當select返回套接字可讀這一條件的時候,則調用 recvfrom
把所讀數據報復制到應用進程緩衝區;
以前的同步非阻塞方式須要用戶進程不停的輪詢,可是IO多路複用不須要不停的輪詢,而是派別人去幫忙循環查詢多個任務的完成狀態,UNIX/Linux 下的 select、poll、epoll
就是幹這個的;select調用是內核級別的,select輪詢相對非阻塞的輪詢的區別在於---前者能夠等待多個socket,能實現同時對多個IO端口進行監聽,當其中任何一個socket的數據準好了,就能返回進行可讀,而後進程再進行recvform系統調用,將數據由內核拷貝到用戶進程,固然這個過程是阻塞的。select或poll調用以後,會阻塞進程,與blocking IO阻塞不一樣在於,此時的select不是等到socket數據所有到達再處理, 而是有了一部分數據(網絡上的數據是分組到達的)就會調用用戶進程來處理。如何知道有一部分數據到達了呢?監視的事情交給了內核,內核負責數據到達的處理。
我認爲上面那句話中存在兩個重要點:1.對多個socket進行監聽,只要任何一個socket數據準備好就返回可讀;2.不等一個socket數據所有到達再處理,而是一部分socket的數據到達了就通知用戶進程;
其實 select、poll、epoll
的原理就是不斷的遍歷所負責的全部的socket完成狀態,當某個socket有數據到達了,就返回可讀並通知用戶進程來處理;
高併發的程序通常使用同步非阻塞方式而非多線程 + 同步阻塞方式。要理解這一點,首先要扯到併發和並行的區別。好比去某部門辦事須要依次去幾個窗口,辦事大廳裏的人數就是併發數,而窗口個數就是並行度。也就是說併發數是指同時進行的任務數(如同時服務的 HTTP 請求),而並行數是能夠同時工做的物理資源數量(如 CPU 核數)。經過合理調度任務的不一樣階段,併發數能夠遠遠大於並行度,這就是區區幾個 CPU 能夠支持上萬個用戶併發請求的奧祕。在這種高併發的狀況下,爲每一個任務(用戶請求)建立一個進程或線程的開銷很是大。而同步非阻塞方式能夠把多個 IO 請求丟到後臺去,這就能夠在一個進程裏服務大量的併發 IO 請求。
首先開啓套接字的信號驅動式IO功能,而且經過 sigaction
系統調用安裝一個信號處理函數,該函數調用將當即返回,當前進程沒有被阻塞,繼續工做;當數據報準備好的時候,內核則爲該進程產生 SIGIO
的信號,隨後既能夠在信號處理函數中調用 recvfrom
讀取數據報,而且通知主循環數據已經準備好等待處理,也能夠通知主循環讓它讀取數據報;(其實就是一個待讀取的通知和待處理的通知);
咱們調用 aio_read
函數,給內核傳遞描述符、緩衝區指針、緩衝區大小和文件偏移,而且告訴內核當整個操做完成時如何通知咱們。該函數調用後當即返回,不被阻塞;