I/O 多路復用底層原理前篇 - 五種IO模型

點贊再看,養(yǎng)成習慣,公眾號搜一搜【一角錢技術】關注更多原創(chuàng)技術文章。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章。

前言

上篇講 BIO、NIO、AIO 的基本概念以及一些常見問題,介紹了 NIO 是同步非阻塞 ,服務器實現(xiàn)模式為一個線程可以處理多個請求(連接),客戶端發(fā)送的連接請求都會注冊到多路復用器selector上,多路復用器輪詢到連接有IO請求就進行處理。那么I/O多路復用器到底是如何實現(xiàn)的?本篇我們來一探究竟。

為了加深對 I/O多路復用機制 的理解,以及了解到多路復用也有局限性,本著打破砂鍋問到底的精神,在這里我們先回顧下 Unix網(wǎng)絡編程中的五種IO模型。下篇再繼續(xù)
IO多路復用進行深入的學習。

  • Blocking IO - 阻塞IO
  • NoneBlocking IO - 非阻塞IO
  • IO multiplexing - IO多路復用
  • signal driven IO - 信號驅動IO
  • asynchronous IO - 異步IO

Unix網(wǎng)絡編程中的五種IO模型

阻塞IO - Blocking IO

最傳統(tǒng)的一種IO模型,即在讀寫數(shù)據(jù)過程中會發(fā)生阻塞現(xiàn)象。

當用戶線程發(fā)出IO請求之后,內核會去查看數(shù)據(jù)是否就緒,如果沒有就緒就會等待數(shù)據(jù)就緒,而用戶線程就會處于阻塞狀態(tài),用戶線程交出CPU。當數(shù)據(jù)就緒之后,內核會將數(shù)據(jù)拷貝到用戶線程,并返回結果給用戶線程,用戶線程才解除block狀態(tài)。

也許有人會說,可以采用多線程+ 阻塞IO 來解決效率問題,但是由于在多線程 + 阻塞IO 中,每個socket對應一個線程,這樣會造成很大的資源占用,并且尤其是對于長連接來說,線程的資源一直不會釋放,如果后面陸續(xù)有很多連接的話,就會造成性能上的瓶頸。

非阻塞IO - NoneBlocking IO

當用戶線程發(fā)起一個 IO 操作后,并不需要等待,而是馬上就得到一個結果。如果結果是一個 error 時,它就知道數(shù)據(jù)還沒有準備好,于是它可以再次發(fā)送 IO 操作。一旦內核中的數(shù)據(jù)準備好了,并且又再次收到了用戶線程的請求,那么它馬上就將數(shù)據(jù)拷貝到了用戶線程,然后返回。

在非阻塞IO 模型中,用戶線程需要不斷地詢問內核數(shù)據(jù)是否就緒,也就說非阻塞IO不會交出CPU,而會一直占用CPU。

對于非阻塞IO就有一個非常嚴重的問題,在while循環(huán)中需要不斷地去詢問內核數(shù)據(jù)是否就緒,這樣會導致CPU占用率非常高,因此一般情況下很少使用while循環(huán)這種方式來讀取數(shù)據(jù)。

  • 非阻塞式主要體現(xiàn)在用戶進程發(fā)起recvfrom系統(tǒng)調用的時候,這個時候系統(tǒng)內核還沒有接收到數(shù)據(jù)報,直接返回錯誤給用戶進程,告訴“當前還沒有數(shù)據(jù)報可達,晚點再來”
  • 用戶進程接收到信息,但是用戶進程不知道什么時候數(shù)據(jù)報可達,于是就開始不斷輪詢(polling)向系統(tǒng)內核發(fā)起recvfrom的系統(tǒng)調用“詢問數(shù)據(jù)來了沒”,如果沒有則繼續(xù)返回錯誤
  • 用戶進程輪詢發(fā)起recvfrom系統(tǒng)調用直至數(shù)據(jù)報可達,這個時候需要等待系統(tǒng)內核復制數(shù)據(jù)報到用戶進程的緩沖區(qū),復制完成之后將返回成功提示

IO多路復用 - IO multiplexing

所謂 I/O 多路復用機制,就是說通過一種機制,可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或寫就緒),能夠通知程序進行相應的讀寫操作。這種機制的使用需要 selectpoll 、 epoll 來配合。

在多路復用IO模型中,會有一個內核線程不斷地去輪詢多個 socket 的狀態(tài),只有當真正讀寫事件發(fā)送時,才真正調用實際的IO讀寫操作。因為在多路復用IO模型中,只需要使用一個線程就可以管理多個socket,系統(tǒng)不需要建立新的進程或者線程,也不必維護這些線程和進程,并且只有真正有讀寫事件進行時,才會使用IO資源,所以它大大減少來資源占用。

  • IO復用模式是使用select或者poll函數(shù)向系統(tǒng)內核發(fā)起調用,阻塞在這兩個系統(tǒng)函數(shù)調用,而不是真正阻塞于實際的IO操作(recvfrom調用才是實際阻塞IO操作的系統(tǒng)調用)
  • 阻塞于select函數(shù)的調用,等待數(shù)據(jù)報套接字變?yōu)榭勺x狀態(tài)
  • 當select套接字返回可讀狀態(tài)的時候,就可以發(fā)起recvfrom調用把數(shù)據(jù)報復制到用戶空間的緩沖區(qū)

信號驅動IO - signal driven IO

在信號驅動IO模型中,當用戶線程發(fā)起一個IO請求操作,會給對應的socket注冊一個信號函數(shù),然后用戶線程會繼續(xù)執(zhí)行,當內核數(shù)據(jù)就緒時會發(fā)送一個信號給用戶線程,用戶線程接收到信號后,便在信號函數(shù)中調用IO讀寫操作來進行實際的IO請求操作。這個一般用于UDP中,對TCP套接字幾乎沒用,原因是該信號產(chǎn)生得過于頻繁,并且該信號的出現(xiàn)并沒有告訴我們發(fā)生了什么請求。

用戶進程可以使用信號方式,當系統(tǒng)內核描述符就緒時將會發(fā)送SIGNO給到用戶空間,這個時候再發(fā)起recvfrom的系統(tǒng)調用等待返回成功提示,流程如下:

  • 先開啟套接字的信號IO啟動功能,并通過一個內置安裝信號處理函數(shù)的signaction系統(tǒng)調用,當發(fā)起調用之后會直接返回;
  • 其次,等待內核從網(wǎng)絡中接收數(shù)據(jù)報之后,向用戶空間發(fā)送當前數(shù)據(jù)可達的信號給信號處理函數(shù);
  • 信號處理函數(shù)接收到信息就發(fā)起recvfrom系統(tǒng)調用等待內核數(shù)據(jù)復制數(shù)據(jù)報到用戶空間的緩沖區(qū);
  • 接收到復制完成的返回成功提示之后,應用進程就可以開始從網(wǎng)絡中讀取數(shù)據(jù)。

異步IO - asynchronous IO

前面四種IO模型實際上都屬于同步IO,只有最后一種是真正的異步IO,因為無論是多路復用IO還是信號驅動模型,IO操作的第2個階段都會引起用戶線程阻塞,也就是內核進行數(shù)據(jù)拷貝的過程都會讓用戶線程阻塞。

  • 由POSIX規(guī)范定義,告知系統(tǒng)內核啟動某個操作,并讓內核在整個操作包含數(shù)據(jù)等待以及數(shù)據(jù)復制過程的完成之后通知用戶進程數(shù)據(jù)已經(jīng)準備完成,可以進行讀取數(shù)據(jù);
  • 與上述的信號IO模型區(qū)分在于異步是通知我們何時IO操作完成,而信號IO是通知我們何時可以啟動一個IO操作

總結

現(xiàn)代計算機服務器操作系統(tǒng)大部分都是基于linxu實現(xiàn),為處理高并發(fā)而采取NIO的模型,對于支持異步IO模型的系統(tǒng)持有不確定因素。

詳見 BIO 、NIO 、AIO 總結

同步與異步的定義

  • 同步:發(fā)起一個fn的調用,需要等待調用結果返回,該調用結果要么是期望的結果要么是異常拋出的結果,可以說是原子性操作(要么成功要么失敗返回)
  • 異步: 發(fā)起一個fn調用,無需等待結果就直接返回,只有當被調用者執(zhí)行處理程序之后通過“喚醒”手段通知調用方獲取結果(喚醒的方式有回調,事件通知等)
  • 小結: 同步和異步關注的是程序之間的通信

阻塞與非阻塞的定義

  • 阻塞: 類比線程阻塞來說明,在并發(fā)多線程爭搶資源的競態(tài)條件下,如果有一個線程已持有鎖,那么當前線程將無法獲取鎖而被掛起,處于等待狀態(tài)
  • 非阻塞: 一旦線程釋放鎖,其他線程將會進入就緒狀態(tài),具備爭搶鎖的資格
  • 小結: 阻塞與非阻塞更關注是程序等待結果的狀態(tài)
  • 由此可知,同步異步與阻塞非阻塞之間不存在關聯(lián),關注的目標是不一樣的

同步IO與異步IO(基于POSIX規(guī)范)

  • 同步IO: 表示應用進程發(fā)起真實的IO操作請求(recvfrom)導致進程一直處于等待狀態(tài),這時候進程被阻塞,直到IO操作完成返回成功提示
  • 異步IO: 表示應用進程發(fā)起真實的IO操作請求(recvfrom)導致進程將直接返回一個錯誤信息,“相當于告訴進程還沒有處理好,好了會通知你”
  • 阻塞IO: 主要是體現(xiàn)發(fā)起IO操作請求通知內核并且內核接收到信號之后如果讓進程等待,那么就是阻塞
  • 非阻塞IO: 發(fā)起IO操作請求的時候不論結果直接告訴進程“不用等待,晚點再來”,那就是非阻塞

IO模型對比

  • 根據(jù)上述的同步與異步IO定義并結合上述的模型可知,只有異步IO模型符合POSIX規(guī)范的異步IO,其他IO模型都存在recvfrom系統(tǒng)調用被內核阻塞,屬于同步IO操作

  • 由此可知,阻塞IO與非阻塞IO可總結如下:

  • 也就是說,要么稱為同步與異步IO,要么稱為上述5種模型的IO說法,注意上述的同步與異步的概念

  • 大部分操作系統(tǒng)都是基于同步IO的方式實現(xiàn),對于支持異步IO模型的操作系統(tǒng)還不確定,在實際工作我們經(jīng)常會說Blocking-IO(阻塞IO)和Non-Blocking-IO(非阻塞IO),極少稱同步IO與異步IO

  • 小結: 同步與異步針對通信機制,阻塞與非阻塞針對程序調用等待結果的狀態(tài)

一句話總結

  • 阻塞IO與非阻塞IO

這是最簡單的模型,一般配合多線程來實現(xiàn)。

  • 多路復用(select/poll/epoll)

一個線程解決多連接的問題

  • 信號驅動IO模型

一種同步IO,更加靈活

  • 異步IO模型

高效主流的模型,效率很高。

文章持續(xù)更新,可以公眾號搜一搜「 一角錢技術 」第一時間閱讀,本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄,歡迎 Star。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容