阻塞I/O
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL90TUkVTOxoVd502Yw40MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwgzMzAjMwgTMxEDOwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
非阻塞IO
同步
使用者程序自己去查詢資料是否就緒
異步
不用自己,核心把資料拷貝到了buffer,通知使用者程式去讀取
多路複用
多個IO事件會注冊到select上,select監聽多個IO,當有就緒的IO事件,select傳回IO的狀态,程式調用IO會阻塞
select的實作方式有如下幾種:
select
select底層,核心存儲fd使用的是數組,預設1024,不能擴容,select底層沒有開辟空間存儲fd,是以每次使用者程式都要将fd傳輸給select
poll
poll過程和select一樣,核心存儲fd使用的是連結清單
epoll
epoll底層開辟了空間使用紅黑樹會存儲fd,是以使用者态不需要拷貝fd到核心态
epoll相比select,poll的優勢
1.select/poll把fd的監聽清單放在使用者空間,由使用者空間管理,導緻在使用者空間和核心空間之間頻繁重複拷貝大量fd;epoll在核心建立fd監聽清單(實際是紅黑樹),每次通過epoll_ctl增删改即可
2.select/poll每當有fd核心事件時,都喚醒目前程序,然後周遊監聽清單全部fd,檢查所有就緒fd并傳回;epoll在有fd核心事件時,通過回調把該fd放到就緒隊列中,隻需傳回該就緒隊列即可,不需要每次周遊全部監聽fd
信号驅動
Netty系列二 Linux IO NIO 多路複用 Reactor模式
首先允許套接字使用信号驅動I/O.在這種模式下,系統調用将會立即傳回,然後程式可以處理其它的工作。當資料準備就緒的時候,系統會向程式程序發送一個SIGIO信号。我們随後就可以在信号處理函數中調用read讀取資料報,并通知主循環資料已經準備好待處理,也可以立即通知主循環,讓它讀取資料報。
異步I/O
相對于同步IO,異步IO不是順序執行的,使用者程序進行aio_read系統調用之後,無論核心資料是否準備好,都會直接傳回給使用者程序,使用者程序就可以做其他的事情。等到socket資料準備好了,核心直接複制資料給程序,然後從核心向程序發送通知。IO兩個階段,程序都是非阻塞的
BIO模式
BIO(同步阻塞)模式流程
流程:
1.伺服器端的Server是一個線程,線程中執行一個死循環來阻塞的監聽用戶端的連接配接請求和通信。
2.當用戶端向伺服器端發送一個連接配接請求後,伺服器端的Server會接受用戶端的請求,ServerSocket.accept()從阻塞中傳回,得到一個與用戶端連接配接相對于的Socket。
3.建構一個handler,将Socket傳入該handler。建立一個線程并啟動該線程,線上程中執行handler,這樣與用戶端的所有的通信以及資料處理都在該線程中執行。當該用戶端和伺服器端完成通信關閉連接配接後,線程就會被銷毀。
4.然後Server繼續執行accept()操作等待新的連接配接請求。
BIO模式優點
1.使用簡單,容易程式設計
2.在多核系統下,能夠充分利用了多核CPU的資源。即,當I/O阻塞系統,但CPU空閑的時候,可以利用多線程使用CPU資源
BIO模式缺點
1.該模式的本質問題在于嚴重依賴線程,但線程Java虛拟機非常寶貴的資源。随着用戶端并發通路量的急劇增加,線程數量的不斷膨脹将伺服器端的性能将急劇下降。
2.線程生命周期的開銷非常高。線程的建立與銷毀并不是沒有代價的。在Linux這樣的作業系統中,線程本質上就是一個程序,建立和銷毀都是重量級的系統函數。
3.資源消耗。記憶體:大量空閑的線程會占用許多記憶體,給垃圾回收器帶來壓力。;CPU:如果你已經擁有足夠多的線程使所有CPU保持忙碌狀态,那麼再建立更過的線程反而會降低性能。
4.穩定性。在可建立線程的數量上存在一個限制。這個限制值将随着平台的不同而不同,并且受多個因素制約:a)JVM的啟動參數、b)Threa的構造函數中請求的棧大小、c)底層作業系統對線程的限制 等。如果破壞了這些限制,那麼很可能抛出OutOfMemoryError異常。
5.線程的切換成本是很高的。作業系統發生線程切換的時候,需要保留線程的上下文,然後執行系統調用。如果線程數過高,不僅會帶來許多無用的上下文切換,還可能導緻執行線程切換的時間甚至會大于線程執行的時間,這時候帶來的表現往往是系統負載偏高、CPU sy(系統CPU)使用率特别高,導緻系統幾乎陷入不可用的狀态。
6.容易造成鋸齒狀的系統負載。一旦線程數量高但外部網絡環境不是很穩定,就很容易造成大量請求的結果同時傳回,激活大量阻塞線程進而使系統負載壓力過大。
7.若是長連接配接的情況下并且用戶端與伺服器端互動并不頻繁的,那麼用戶端和伺服器端的連接配接會一直保留着,對應的線程也就一直存在在,但因為不頻繁的通信,導緻大量線程在大量時間内都處于空置狀态。
BIO模式适用場景
如果你有少量的連接配接使用非常高的帶寬,一次發送大量的資料,也許典型的IO伺服器實作可能非常契合
Rector模式
基于I/O多路複用模型,多個連接配接共用一個阻塞對象,應用程式隻需要在一個阻塞對象等待,無需阻塞等待所有連接配接。當某個連接配接有新的資料可以處理時,作業系統通知應用程式,線程從阻塞狀态傳回,開始進行業務處理
單Reactor單線程
步驟
Reactor是一個線程對象,該線程會啟動事件循環,并使用Selector來實作IO的多路複用,注冊一個Acceptor事件處理器到Reactor中,Acceptor事件處理器所關注的事件是ACCEPT事件,這樣Reactor會監聽用戶端向伺服器端發起的連接配接請求事件(ACCEPT事件)。
1.Reactor對象通過Select監聽用戶端Accept事件,收到事件後通過Dispatch進行分發給對應的Acceptor進行處理。Acceptor處理器通過accept()方法得到與這個用戶端對應的連接配接(SocketChannel),然後将該連接配接所關注的READ事件以及對應的READ事件處理器注冊到Reactor中。
2.當Reactor監聽到讀寫事件時,則Reactor會分發對應的讀寫Handler處理。
3.每當處理完所有就緒的感興趣的I/O事件後,Reactor線程會再次執行select()阻塞等待新的事件就緒并将其分派給對應處理器進行處理
注意,Reactor的單線程模式的單線程主要是針對于I/O操作而言,也就是是以的I/O的accept()、read()、write()以及connect()操作都在一個線程上完成的。
優點
模型簡單,沒有多線程、程序通信、競争的問題,全部都在一個線程中完成
缺點
1.性能問題,隻有一個線程,無法完全發揮多核 CPU 的性能
2.可靠性問題,線程意外終止,或者進入死循環,會導緻整個系統通信子產品不可用
單Reactor多線程
相比于單Reactor單線程模式,單Reactor多線程增加了線程池來處理非I/O操作(業務處理),這樣能夠提高Reactor線程的I/O響應,不至于因為一些耗時的業務邏輯而延遲對後面I/O請求的處理
優點
可以充分的利用多核cpu 的處理能力
缺點
多線程資料共享和通路比較複雜,Reactor處理所有的事件的監聽和響應,在單線程運作, 在高并發場景容易出現性能瓶頸
主從Reactor多線程
步驟
1.注冊一個Acceptor事件(處理accept事件)處理器到MainReactor中,啟動MainReactor的事件循環
2.用戶端向服務端發起一個連接配接請求,MainReactor監聽到該Accept事件并将該Acceptor事件派發給Acceptor處理器進行處理。Acceptor處理器通過accept()方法得到該連接配接的SocketChannel,然後将SocketChannel通過dispatch派發給subReactor
3.SubReactor線程池配置設定一個SubReactor線程給這個SocketChannel,将這個SocketChannel的R/W事件到SubReactor的selector中。
4.當有I/O事件就緒時,相關的SubReactor就将事件派發給相應的Handler。這裡的subReactor線程隻負責完成I/O的read()操作,業務處理是線上程池中完成的。
注意,是以的I/O操作(包括,I/O的accept()、read()、write()以及connect()操作)依舊還是在Reactor線程(mainReactor線程或subReactor線程)中完成的。Thread Pool(線程池)僅用來處理非I/O操作的邏輯
優點
1.父線程與子線程的資料互動簡單職責明确,父線程隻需要接收新連接配接,子線程完成後續的業務處理。
2.父線程與子線程的資料互動簡單,Reactor主線程隻需要把新連接配接傳給子線程,子線程無需傳回資料。