-
BIO模型
在Linux中,默認情況下所有socket都是阻塞模式,用戶執行緒呼叫系統函式read()【system call】,內核開始準備資料(從磁盤/網路獲取資料),內核準備資料完成后,用戶執行緒完成資料從內核拷貝到用戶空間的應用程式緩沖區,資料拷貝完成后,請求才回傳,從發起read請求到完成內核到應用程式的拷貝,整個程序都是阻塞的,
為了減輕執行緒阻塞的弊端,實際上,每個Read/Write請求都會分配單獨執行緒進行單獨處理,在低并發時期,這種每個請求每執行緒的處理方式是可以應付的,但是如果在高并發期間(如:業務高峰期),就會分配大量的執行緒完成請求處理,因此會帶來非常大的性能損耗, -
NIO模型
用戶執行緒在發起Read請求后立即回傳,不用等待內核準備資料的程序,如果Read請求沒讀取到資料,用戶執行緒會不斷輪詢發起Read請求,直到資料到達(內核準備好資料)后才停止輪詢,
非阻塞IO模型雖然避免了由于執行緒阻塞問題帶來的大量執行緒消耗,但是頻繁的重復輪詢大大增加了請求次數,對CPU消耗也比較明顯,
-
多路復用模型
多路復用IO模型,建立在多路事件分離函式select,poll,epoll之上,在發起read請求前,先更新select的socket監控串列,然后等待select(或poll或epoll)函式回傳(此程序是阻塞的),當某個socket有資料到達時,select函式回傳,此時用戶執行緒才正式發起read或write請求,處理資料,這種模式用一個專門的監視執行緒去檢查多個socket,如果某個socket有資料到達就交給作業執行緒處理,由于等待Socket資料到達程序非常耗時,所以這種方式解決了阻塞IO模型一個Socket連接就需要一個執行緒的問題,也不存在非阻塞IO模型忙輪詢帶來的CPU性能損耗的問題,

多路復用的本質,在我看來其實就是通過盡可能少(預期一次)的系統呼叫(system call),就可以拿到所有socket的狀態(是否可讀),然后程式只需要對那些回傳狀態為可讀或可寫的socket進行處理,
1 // NIO核心代碼 2 // 初始化 3 channel = ServerSocketChannel.open(); 4 channel.bind(port); 5 channel.configureBlocking(false); 6 // selector 注冊accept事件 7 selector = Selector.open(); 8 channel.register(selector, SelectionKey.OP_ACCEPT); 9 while(true){ 10 while(selector.select(timeout)>0){ // 有新的事件 11 // 獲取到可處理的 Socket 12 Set<SelectionKey> keySet = selector.selectedKeys(); 13 Iterator<SelectionKey> iter = keySet.iterator(); 14 while (iter.hasNext()) { // 回圈處理,處理之后應該從迭代器中移除 15 SelectionKey key = iter.next(); 16 iter.remove(); 17 if (key.isAcceptable()) { // 新連接 acceptHandle(key); } 18 else if (key.isReadable()) { // 讀事件 readHandle(key); } 19 else if (key.isWritable()) { // 寫事件 writeHandle(key); } 20 } 21 } 22 }
View Code實際上,linux的多路復用有三種實作方式,select和poll以及epoll,它們之間的關系是進化關系,性能都是遞進的,
-
select
select 是作業系統提供的系統呼叫函式,通過它,我們可以把一個檔案描述符的陣列【最大為1024個】發給作業系統內核, 讓內核去遍歷,確定哪個檔案描述符可以讀寫,實際上只是打了一個標志,哪些可讀可寫,所以在用戶程式獲取select的回傳值的時候,仍然需要遍歷檔案描述符的陣列具有哪些可處理的事件,
select模型的優化在于將所有的檔案描述符【其實就是正在監聽的socket連接】批量的傳給了內核,降低了系統呼叫,減少了內核態與用戶態的切換,而select的弊端就在于每次最多只能傳輸1024個檔案描述符,這在一些高并發場景【redis快取】下,仍然是不夠看的,此外select 在內核層仍然是通過遍歷的方式檢查檔案描述符的就緒狀態,是個同步程序,只不過無系統呼叫切換背景關系的開銷, -
poll
poll對于select來講,最大的區別在于只是將每次只能傳輸1024個檔案描述符的限制去掉了
-
epoll
epoll是在select和poll的基礎上做出的演進,他主要針對以下3點做出了改進
1.內核中保存一份檔案描述符的集合,無需用戶程式每次懂重新傳遞【不需要拷貝】;
2.內核不再通過輪訓的方式獲取事件就緒的檔案描述符,而是通過事件回呼的方式將就緒事件放入到一個就緒佇列中【內核程式不需要進行O(N)的遍歷】;
3.內核僅會將就緒佇列中的檔案描述符回傳給用戶程式,用戶程式直接處理該描述符對應的事件即可【用戶程式不需要再次進行O(N)的遍歷,內核將不必要的檔案描述符過濾了,因此發生的記憶體拷貝更少了】,
-
select、poll、epoll對比

AIO
不論是阻塞IO亦或是NIO,它們都是同步IO,即當內核將資料準備好的時候,都是由用戶執行緒將資料拷貝到用戶空間,除此之外還有一個異步IO,即非阻塞IO,當資料準備好的時候,不需要用戶執行緒將資料拷貝到程式的運行空間,而是直接由內核執行緒完成資料的拷貝,
參考檔案:
1.徹底搞懂IO多路復用
2.忘了一些重要的文章~~~
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/499492.html
標籤:Linux
