網站首頁 編程語言 正文
在講解這三個I/O操作之前先普及一下I/O的基礎知識,不然聽后面的點會產生困惑,有基礎的朋友可以從BIO開始閱讀
什么是I/O操作?
I/O(Input/Output)操作指的是計算機系統與外部設備或程序之間的數據傳輸。I/O 操作包括讀取和寫入數據,用于在計算機系統和外部環境之間進行信息交換。
I/O 操作可以分為兩大類:
-
輸入操作(Input):
- 從外部設備或其他程序讀取數據到計算機系統中。
- 例子:
- 從鍵盤輸入數據。
- 從磁盤讀取文件內容。
- 從網絡接收數據。
-
輸出操作(Output):
- 將計算機系統中的數據發送到外部設備或其他程序。
- 例子:
- 向屏幕打印輸出信息。
- 將數據寫入磁盤文件。
- 向網絡發送數據。
I/O 操作是計算機系統中非常重要的一部分,因為計算機系統通常需要與外部世界進行交互。外部設備包括鍵盤、鼠標、磁盤驅動器、網絡接口等,而程序之間的數據傳輸也屬于 I/O 操作。
在計算機中,I/O 操作的速度相對較慢,因此在編程中,優化和有效地管理 I/O 操作對于提高系統性能和響應速度至關重要。對于高效的 I/O 操作,涉及到使用適當的 I/O 模型、緩沖、異步操作等技術。
用戶空間與內核空間
操作系統的核心是內核,獨立于普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操心系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。
進程不能直接訪問硬件設備,當進程需要訪問硬件設備(比如讀取磁盤文件,接收網絡數據等等)時,必須由用戶態模式切換至內核態模式,通過系統調用訪問硬件設備。
文件描述符fd
文件描述符(File descriptor)是計算機科學中的一個術語,是一個用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統。
應用程序中如何進行I/O操作?
我們程序中的IO讀寫其實調用的是操作系統內核中的read&write兩大系統調用
例如使用Java通過socket進行網絡I/O,也必須依賴系統內核
具體步驟:
- 網卡收到網線傳來的網絡數據,并將數據寫入內存
- 數據寫入內存后,網卡向cpu發送中斷信號(通知發生特定事件的一種機制),操作系統遍能得知有新數據到來,再通過網卡中斷程序去處理數據
- 將內存中的數據寫入到對應的socket的接收緩沖區中
- 當接收緩沖區的數據寫好后,應用程序開始進行數據處理
public class SocketServer {
public static void main(String[] args) throws Exception {
// 監聽指定的端口
int port = 8080;
ServerSocket server = new ServerSocket(port);
// server將一直等待連接的到來
Socket socket = server.accept();
// 建立好連接后,從socket中獲取輸入流,并建立緩沖區進行讀取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
// 此處的read操作是阻塞操作
while ((len = inputStream.read(bytes)) != -1) {
//獲取數據進行處理
String message = new String(bytes, 0, len,"UTF-8");
}
// socket、server,流關閉操作,省略不表
}
}
?? 以下幾種IO模型的區分點在于:
- 數據等待階段
- 將數據從內核空間的buffer拷貝到用戶空間進程的buffer
阻塞IO(blocking IO)
特點:在IO執行的兩個階段都被block了
造成的影響
? 意味著如果在等待客戶端的連接或者處理讀寫請求時,服務端不能去做任何事情,當前操作完成,假如服務端在等待客戶端的寫操作,而客戶端一直沒響應,那么服務端就“卡死了”(不能處理其他客戶端的請求)
解決方法
? 使用多線程,每當一個客戶端連接上服務端,就專門開啟一個線程處理這個客戶端的請求,服務端能夠正常處理每一個客戶端的請求,主線程不會被阻塞
? 缺點:假如同時有1000個客戶端同時訪問服務端,就需要開啟1000個線程去處理,并且這1000個線程同時阻塞等待客戶端的I/O操作,嚴重浪費了CPU和內存的資源
總結
-
優點
- 編程模型簡單,易于理解
- 適用于低并發,低負載的場景
-
缺點
- 阻塞 I/O 會導致線程被阻塞,無法應對高并發場景
- 在高并發環境下,阻塞 I/O 可能導致大量線程被創建,增加系統開銷
非阻塞IO(nonblocking IO)
特點:如果數據尚未準備好,不會一直等待,而是一邊向下執行任務一邊向內核詢問數據準備好了沒
舉個栗子
現在你是一個服務員(服務端)。當一個顧客坐下后點菜,然后開始等待他的菜做好。在這個等待的過程中,你可以去做其他事情,不需要一直等在那里,但是你需要隔一段時間就去問廚師起先顧客的菜好了嗎。
優點:解決了BIO的阻塞問題,在沒有I/O操作時,不會發生阻塞,會繼續處理其他任務,提高并發能力
缺點:一直去輪詢I/O操作是否完成,會造成CPU資源的浪費。就像是一個顧客剛點完菜,服務員就一直問菜煮好了嗎??????(內心:又不是預制菜??,哪有那么快),菜準備的越久,越浪費服務員的精力。
I/O多路復用
無論是阻塞I0還是非阻塞I0,用戶應用在一階段都需要調用recvfrom來獲取數據,差別在于無數據時的處理方案:
- 如果調用recvfrom時,恰好沒有數據,阻塞I0會使進程阻塞,非阻塞I0使CPU空轉,都不能充分發揮CPU的作用。
- 如果調用recvfrom時,恰好有數據,則用戶進程可以直接進入第二階段,讀取并處理數據
比如服務端處理客戶端Socket請求時,在單線程情況下,只能依次處理每一個socket,如果正在處理的socket恰好未就緒(數據不可讀或不可寫),線程就會被阻塞,所有其它客戶端socket都必須等待,性能自然會很差。
這就像服務員給顧客點餐,分兩步:
- 顧客思考要吃什么(等待數據就緒)
- 顧客想好了,開始點餐(讀取數據)要提高效率有幾種辦法?
-
方案一:增加更多服務員(多線程)
-
方案二:不排隊,誰想好了吃什么(數據就緒了),服務員就給誰點餐(用戶應用就去讀取數據)
那么問題來了:用戶進程如何知道內核中數據是否準備好了?這就需要使用上面開頭說的文件描述符fd
I/O多路復用:是利用單個線程來同時監聽多個FD,并在某個FD可讀、可寫時得到通知,從而避免無效的等待,充分利用CPU資源。
問題:
在IO多路復用的時候,處理數據的兩個階段都需要阻塞等待,那與非阻塞又有什么區別呢?
答:非阻塞的痛點在于什么?雖然解決了單個線程在進行I/O時會被阻塞的問題,但是依然沒有解決單線程下無法處理多個socket的問題。但是I/O多路復用可以同時處理多個socket。
I/O多路復用模型的實現
select
//定義類型別名_-fd_mask,本質是longint
typedef long int __fd_mask;
/*fd_set記錄要監聽的fd集合,及其對應狀態*/
typedef struct {
//fds_bits是long類型數組,長度為1024/32=32
//共1024個bit位,每個bit位代表一個fd,0代表未就緒,1代表就緒
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS] ;
//...
}fd_set;
//select函數,用于監聽多個fd的集合
int select(
intnfds,//要監視的fd_set的最大fd+1
fd_set*readfds,//要監聽讀事件的fd集合
fd_set*writefds,//要監聽寫事件的fd集合
fd_set*exceptfds,// 要監聽異常事件的fd集合
//超時時間,null-永不超時;0-不阻塞等待;大于0-固定等待時間
struct timeval *timeout
);
select存在的問題:
- 需要將整個fd_set從用戶空間拷貝到內核空間,select結束還要再次拷貝回用戶空間
- select無法得知具體是哪個fd就緒,需要遍歷整個fd_set
- fd_set監聽的fd數量不能超過1024
當用戶調用了select,那么整個進程會被block,而同時,系統會監視所有select負責的socket,當任何一個socket的數據準備好了,select就會返回。
poll
//pollfd中的事件類型
#define POLLIN //可讀事件
#define POLLOUT //可寫事件
#define POLLERR //錯誤事件
#define POLLNVAL //fd未打開
//pollfd結構
struct pollfd{
/*要監聽的fd*/
short int events;/*要監聽事件類型:讀、寫、異常*/
short int revents;/*實際發生的事件類型*/
};
//Poll函數
int poll (
struct pollfd* fds,//pollfd數組,可以自定義大小
nfds_t nfds,//數組元素個數
int timeout//超時時間
);
I0流程:
- 創建pollfd數組,向其中添加關注的fd信息,數組大小自定義
- 調用poll函數,將pollfd數組拷貝到內核空間,轉鏈表存儲,無上限
- 內核遍歷fd,判斷是否就緒
- 數據就緒或超時后,拷貝polfd數組到用戶空間,返回就緒fd數量n
- 用戶進程判斷n是否大于0
- 大于0則遍歷pollfd數組,找到就緒的fd
與select對比:
- 優點:select模式中的fd_set大小固定為1024,而pollfd在內核中采用
鏈表,理論上無上限 - 缺點:監聽FD越多,每次遍歷消耗時間也越久
epoll
步驟
- 創建epoll實例
- 添加要監聽的FD到紅黑樹,關聯callback
- epoll_wait等待FD就緒,如果有FD就緒后,會將FD添加到list_head中,在用戶調用epoll_wait后就會將這些就緒的FD拷貝到event數組中,相比于前兩種監聽模式,epoll不需要遍歷所有的FD集合就知道哪些FD就緒
總結
select模式存在的三個問題:
- 能監聽的FD最大不超過1024
- 每次select都需要把所有要監聽的FD都拷貝到內核空間
- 每次都要遍歷所有的FD來判斷就緒狀態
poll存在的問題:
- poll雖然解決了select監聽FD上限的問題,但是隨著監聽FD數量的上升,性能反而會下降
epoll如何解決這些問題:
解決FD上限問題:基于epoll實例中的紅黑數
保存要監聽的FD,理論上無上限,而增刪改查銷量都非常高,性能不會隨著FD數量增多反而下降
FD拷貝問題:每一個FD只需要執行一次epoll_ctl添加到紅黑樹,以后每次epoll_wait無需傳遞任何參數,無需重復拷貝FD到內核空間
查找FD效率低問題:內核會將就緒的FD直接拷貝到用戶空間的指定位置,用戶進程無需遍歷所有FD就能知道就緒的FD是誰
異步IO(async IO)
上面三種IO模型都有一個共同的缺點:當系統中數據準備好的時候,recvfrom會將數據從內核空間拷貝到用戶內存中,在這段時間內,進程是被阻塞的
AIO 就是用來解決數據拷貝階段的阻塞問題
- 同步意味著,在進行讀寫操作時,線程需要等待結果,還是相當于閑置
- 異步意味著,在進行讀寫操作時,線程不必等待結果,而是將來由操作系統來通過回調方式由另外的線程來獲得結果
異步模型需要底層操作系統(Kernel)提供支持
- Windows 系統通過 IOCP 實現了真正的異步 IO
- Linux 系統異步 IO 在 2.6 版本引入,但其底層實現還是用多路復用模擬了異步 IO,性能沒有優勢
異步IO整個操作都是非阻塞的,用戶進程調用完異步API后就可以去做其它事情,內核等待數據就緒并拷貝到用戶空間后才會遞交信號,通知用戶進程
原文鏈接:https://blog.csdn.net/m0_62963408/article/details/136074921
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-06-01 詳解C語言中二分查找的運用技巧_C 語言
- 2021-12-10 golang?db事務的統一封裝的實現_Golang
- 2022-06-12 python?包之?re?正則匹配教程分享_python
- 2022-08-14 Nginx安裝配置詳解_nginx
- 2021-12-07 c++代碼各種注釋示例詳解_C 語言
- 2023-02-23 GO中的條件變量sync.Cond詳解_Golang
- 2022-05-06 CSRF攻擊是什么?如何防范CSRF攻擊?_安全相關
- 2022-01-17 git git版本回退 回滾 解決方案
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支