您的位置:首頁>正文

IO是進階肯定會學到的!此篇乾貨萬字長文看完必定能入門!最全!

模型應用

一、select庫

select庫是各個版本的linux和windows平臺都支援的基本事件驅動模型庫, 並且在介面的定義上也基本相同, 只是部分參數的含義略有差異。

使用select庫的一般步驟:創建所關注事件的描述集合。 對於一個描述符, 可以關注其上面的讀事件、寫事件以及異常發生事件, 所以要創建三類事件描述符集合, 分別用來收集讀事件的描述符、寫事件的描述符和異常事件的描述符。

三、epoll庫

epoll庫是Nginx伺服器支持的高性能事件之一,它是公認的非常優秀的時間驅動模型,和poll和select有很大的不同,屬於poll庫的一個變種,他們的處理方式都是創建一個待處理事件列表,然後把這個事件列表發送給內核,返回的時候,再去輪詢檢查這個列表,以判斷事件是否發生。如果這樣的描述符在比較多的應用中,效率就顯得低下了,epoll是描述符列表的管理交給內核負責,一旦某種事件發生,內核會把發生事件的描述符清單通知給進程,這樣就避免了輪詢整個描述符列表,epoll庫得到事件列表,就開始進行事件處理了。

epoll的用法與poll幾乎一樣

epoll解決了select的三個缺點,是目前最好的IO多工解決方案。

檔描述符fd

檔描述符(File descriptor)是電腦科學中的一個術語,是一個用於表述指向檔的引用的抽象化概念。

檔描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開檔的記錄表。當程式打開一個現有檔或者創建一個新檔時,內核向進程返回一個檔描述符。在程式設計中,一些涉及底層的程式編寫往往會圍繞著檔描述符展開。但是檔描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。

本文討論的背景是Linux環境下的network IO。

Stevens在文章中一共比較了五種IO Model:

blocking IO

nonblocking IO

IO multiplexing

signal driven IO

asynchronous IO

由於signal driven IO在實際中並不常用,所以我這只提及剩下的四種IO Model。

再說一下IO發生時涉及的物件和步驟。

non-blocking IO(非阻塞IO)

linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

從圖中可以看出,當使用者進程發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者進程,而是立刻返回一個error。從使用者進程角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。使用者進程判斷結果是一個error時,它就知道資料還沒有準備好,於是它可以再次發送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者進程的system call,那麼它馬上就將資料拷貝到了使用者記憶體,然後返回。

使用者進程發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對使用者進程產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者進程發送一個signal,告訴它read操作完成了。

各個IO Model的比較

到目前為止,已經將四個IO Model都介紹完了。現在回過頭來回答最初的那幾個問題:blocking和non-blocking的區別在哪,synchronous IO和asynchronous IO的區別在哪。

經過上面的介紹,會發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,但是它仍然要求進程去主動的check,並且當資料準備完成以後,也需要進程主動的再次調用recvfrom來將資料拷貝到使用者記憶體。而asynchronous IO則完全不同。它就像是使用者進程將整個IO操作交給了他人(kernel)完成,然後他人做完後發信號通知。在此期間,使用者進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝資料。

五種IO模型比較:

epoll可以同時支援水準觸發和邊緣觸發(Edge Triggered,只告訴進程哪些檔描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有採取行動,那麼它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當複雜。

另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法後,內核才對所有監視的檔描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個檔描述符,一旦基於某個檔描述符就緒時,內核會採用類似callback的回檔機制,迅速啟動這個檔描述符,當進程調用epoll_wait()時便得到通知。

所以市面上上見到的所謂的非同步IO,比如nginx、Tornado、等,我們叫它非同步IO,實際上是IO多工。

就掉出了系統的調度佇列,暫時不會去瓜分CPU寶貴的時間片了。

為了瞭解阻塞是如何進行的,我們來討論緩衝區,以及內核緩衝區,最終把I/O事件解釋清楚。緩衝區的引入是為

了減少頻繁I/O操作而引起頻繁的系統調用(你知道它很慢的),當你操作一個流時,更多的是以緩衝區為單位進

行操作,這是相對於用戶空間而言。對於內核來說,也需要緩衝區。

假設有一個管道,進程A為管道的寫入方,B為管道的讀出方。

假設一開始內核緩衝區是空的,B作為讀出方,被阻塞著。然後首先A往管道寫入,這時候內核緩衝區由空的狀態變

到非空狀態,內核就會產生一個事件告訴B該醒來了,這個事件姑且稱之為“緩衝區非空”。

以把“忙”字去掉了)。代碼長這樣:

while true {

select(streams[])

for i in streams[] {

if i has data

read until unavailable

}

}

而epoll只關心緩衝區非滿和緩衝區非空事件)。

一個epoll模式的代碼大概的樣子是:

while true {

active_stream[] = epoll_wait(epollfd)

for i in active_stream[] {

read or write till unavailable

}

}

(1) 如果內核緩衝區沒有資料--->等待--->資料到了內核緩衝區,轉到使用者進程緩衝區;

(2) 如果先用select監聽到某個檔描述符對應的內核緩衝區有了資料,當我們再調用accept或recv時,直接將資料轉到使用者緩衝區。

思考2: 如何在某一個client端退出後,不影響server端和其它客戶端正常交流

linux:

三、epoll庫

epoll庫是Nginx伺服器支持的高性能事件之一,它是公認的非常優秀的時間驅動模型,和poll和select有很大的不同,屬於poll庫的一個變種,他們的處理方式都是創建一個待處理事件列表,然後把這個事件列表發送給內核,返回的時候,再去輪詢檢查這個列表,以判斷事件是否發生。如果這樣的描述符在比較多的應用中,效率就顯得低下了,epoll是描述符列表的管理交給內核負責,一旦某種事件發生,內核會把發生事件的描述符清單通知給進程,這樣就避免了輪詢整個描述符列表,epoll庫得到事件列表,就開始進行事件處理了。

epoll的用法與poll幾乎一樣

epoll解決了select的三個缺點,是目前最好的IO多工解決方案。

檔描述符fd

檔描述符(File descriptor)是電腦科學中的一個術語,是一個用於表述指向檔的引用的抽象化概念。

檔描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開檔的記錄表。當程式打開一個現有檔或者創建一個新檔時,內核向進程返回一個檔描述符。在程式設計中,一些涉及底層的程式編寫往往會圍繞著檔描述符展開。但是檔描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。

本文討論的背景是Linux環境下的network IO。

Stevens在文章中一共比較了五種IO Model:

blocking IO

nonblocking IO

IO multiplexing

signal driven IO

asynchronous IO

由於signal driven IO在實際中並不常用,所以我這只提及剩下的四種IO Model。

再說一下IO發生時涉及的物件和步驟。

non-blocking IO(非阻塞IO)

linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

從圖中可以看出,當使用者進程發出read操作時,如果kernel中的資料還沒有準備好,那麼它並不會block使用者進程,而是立刻返回一個error。從使用者進程角度講 ,它發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。使用者進程判斷結果是一個error時,它就知道資料還沒有準備好,於是它可以再次發送read操作。一旦kernel中的資料準備好了,並且又再次收到了使用者進程的system call,那麼它馬上就將資料拷貝到了使用者記憶體,然後返回。

使用者進程發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當它受到一個asynchronous read之後,首先它會立刻返回,所以不會對使用者進程產生任何block。然後,kernel會等待資料準備完成,然後將資料拷貝到使用者記憶體,當這一切都完成之後,kernel會給使用者進程發送一個signal,告訴它read操作完成了。

各個IO Model的比較

到目前為止,已經將四個IO Model都介紹完了。現在回過頭來回答最初的那幾個問題:blocking和non-blocking的區別在哪,synchronous IO和asynchronous IO的區別在哪。

經過上面的介紹,會發現non-blocking IO和asynchronous IO的區別還是很明顯的。在non-blocking IO中,雖然進程大部分時間都不會被block,但是它仍然要求進程去主動的check,並且當資料準備完成以後,也需要進程主動的再次調用recvfrom來將資料拷貝到使用者記憶體。而asynchronous IO則完全不同。它就像是使用者進程將整個IO操作交給了他人(kernel)完成,然後他人做完後發信號通知。在此期間,使用者進程不需要去檢查IO操作的狀態,也不需要主動的去拷貝資料。

五種IO模型比較:

epoll可以同時支援水準觸發和邊緣觸發(Edge Triggered,只告訴進程哪些檔描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有採取行動,那麼它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當複雜。

另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法後,內核才對所有監視的檔描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個檔描述符,一旦基於某個檔描述符就緒時,內核會採用類似callback的回檔機制,迅速啟動這個檔描述符,當進程調用epoll_wait()時便得到通知。

所以市面上上見到的所謂的非同步IO,比如nginx、Tornado、等,我們叫它非同步IO,實際上是IO多工。

就掉出了系統的調度佇列,暫時不會去瓜分CPU寶貴的時間片了。

為了瞭解阻塞是如何進行的,我們來討論緩衝區,以及內核緩衝區,最終把I/O事件解釋清楚。緩衝區的引入是為

了減少頻繁I/O操作而引起頻繁的系統調用(你知道它很慢的),當你操作一個流時,更多的是以緩衝區為單位進

行操作,這是相對於用戶空間而言。對於內核來說,也需要緩衝區。

假設有一個管道,進程A為管道的寫入方,B為管道的讀出方。

假設一開始內核緩衝區是空的,B作為讀出方,被阻塞著。然後首先A往管道寫入,這時候內核緩衝區由空的狀態變

到非空狀態,內核就會產生一個事件告訴B該醒來了,這個事件姑且稱之為“緩衝區非空”。

以把“忙”字去掉了)。代碼長這樣:

while true {

select(streams[])

for i in streams[] {

if i has data

read until unavailable

}

}

而epoll只關心緩衝區非滿和緩衝區非空事件)。

一個epoll模式的代碼大概的樣子是:

while true {

active_stream[] = epoll_wait(epollfd)

for i in active_stream[] {

read or write till unavailable

}

}

(1) 如果內核緩衝區沒有資料--->等待--->資料到了內核緩衝區,轉到使用者進程緩衝區;

(2) 如果先用select監聽到某個檔描述符對應的內核緩衝區有了資料,當我們再調用accept或recv時,直接將資料轉到使用者緩衝區。

思考2: 如何在某一個client端退出後,不影響server端和其它客戶端正常交流

linux:

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示