目錄
前言
文件描述符
為什么要多種io模型
同步IO
1.阻塞IO
2.非阻塞IO
3.多路復用IO(事件驅動IO)
select:
poll:
epoll:
4.信號驅動IO
異步IO
區別
前言
文件描述符
首先我們了解一下文件描述符是什么:在linux下一切皆文件,進程是通過文件描述符(file descriptors)來訪問文件的,。默認有三個文件描述符:0(標準輸入),1(標準輸出),2(標準錯誤)。再打開一個新的文件的話,它的文件描述符就++。
為什么要多種io模型
網絡IO,會涉及到兩個系統對象,一個是用戶空間調用IO的進程或線程,另一個是內核空間的內核系統,比如發生IO操作read時,它會經歷兩個階段。
1.等待數據準備就緒2.將數據從內核拷貝到進程或線程中
因為在以上兩個階段上各有不同的情況,所以出現了多種網絡 IO 模型。
同步IO
1.阻塞IO
在linux下,所有socket默認都是阻塞的,我要向一個文件描述符做read操作,此時內核里沒有數據就緒,那么這個時候用戶進程就會阻塞,直到內核數據就緒了,會將數據從內核拷貝到用戶內存,然后返回結果,此時用戶進程解除阻塞狀態。
優點:開發簡單,在阻塞期間用戶線程掛起,掛起不會占用CPU資源。
缺點:不適合大并發,開銷會非常大。
但是如果有多個client阻塞IO就不適用了,所以引用了多線程,但是如果數據規模太大了,會很占用系統資源,而且線程和進程容易進入假死狀態。如果用線程池的話,數據規模非常非常大,線程池可能緩解部分壓力,但是不能解決所有問題,所以我們要引入其它io模型。
2.非阻塞IO
設置socket為非阻塞,如果內核還未將數據準備好,系統調用仍然會直接返回。我要向一個文件描述符做read操作,如果有數據,則成功讀取返回,如果沒有數據,也返回,但帶上錯誤碼。使用這種方式的話,我們做讀取,就必須每隔一段時間去看看,叫非阻塞輪詢檢測。
但是輪詢提高CPU占用率,并且系統也提供了select()多路復用模式,可以一次檢測多個連接是否活躍,所以非阻塞IO一般在特定場景使用。
優點:每次發起 IO 調用,在內核等待數據的過程中可以立即返回,用戶線程不會阻塞,實時性好
?缺點:多個線程不斷輪詢內核是否有數據,占用大量 CPU 資源,效率低。
3.多路復用IO(事件驅動IO)
單個進程/線程就可以同時處理多個IO請求,一個進程/線程可以監視多個文件句柄。
而多路復用IO利用了操作系統提供的一些機制,如select、poll、epoll,來同時監視多個I/O事件的狀態。
select:
底層是數組,采用輪詢,當用戶進程調用了 select(每次調用select()方法,都需要把 fd 集合從用戶態拷貝到內核態,并進行遍歷。),那么整個進程會被阻塞,一旦某個文件句柄就緒,select 就會返回。這個時候用戶進程再調用 read 操作,將數據從內核拷貝到用戶進程。
poll:
poll用鏈表方式存fd,沒有最大數量fd限制,其余和select一樣。
epoll:
只會返回就緒的文件描述符,而不是遍歷整個文件描述符集合。
紅黑樹方式存fd,沒有最大數量fd限制,可保存所有待檢測的socket,所以只需要拷貝一次,減少了內核和用戶空間大量的數據拷貝和內存分配,回調方式不是輪詢,不會因為fd增多性能下降。缺點:只能在Linux下工作。
這里補充一個知識點:
Reactor(反應堆),三部分組成,多路復用器(同時阻塞io socket),事件派發,事件處理(回調處理)。
4.信號驅動IO
內核將數據準備好的時候,使用SIGIO信號通知應用程序進行IO操作。
異步IO
用戶進程發起操作之后,就可以開始去做其它的事。而另一方面,當內核收到read后首先它會立刻返回,所以不會對用戶進程產生任何阻塞。然后內核會等待數據準備完成,然后將數據拷貝到用戶內存,然后會給用戶進程發送一個信號,告訴它操作完成了。
區別
阻塞IO,非阻塞IO,多路復用IO,信號驅動IO這四種的主要區別在第一階段,他們在第二階段是一樣的:數據從內核緩沖區復制到調用者緩沖區期間都被阻塞住。異步 IO 都是非阻塞。
同步和異步,看是誰把內核緩沖區數據拷貝到用戶緩沖區的,如果不是自己寫代碼實現的,那就是異步。