流:就是可以進行內核操作的對象。比如文件,socket,pipe
I/O操作:從流中讀取數據,往流中寫入數據。
阻塞模式:要執行的操作無法進行,一直等待條件滿足時再執行。等待過程中,無操作,靜靜等待。(經濟簡單)
阻塞模式下,內核對於I/o事件的處理是阻塞或者喚醒。
非阻塞模式下,內核對於I/o事件交給其他對象處理,甚至直接忽略。
非阻塞忙輪詢:等待過程中,一直查詢執行條件是否滿足,循環往復。直到條件滿足再執行。(浪費時間精力)
緩衝區:目的是爲了減少I/O操作引起的系統頻繁調用(很慢!)當你操作一個流時,更多的是以緩衝區爲單位進行操作。(用戶和內核都需要緩衝區)
狀態(管道事件) | 進程A(往管道的寫入數據) | 管道(數據的存儲,流動) | 進程B(從管道讀取數據) |
---|---|---|---|
緩衝區(管道)空事件 | A 沒有往管道寫數據 | 管道是空的 | B無法讀取數據,被阻塞着,就是B睡眠了。 |
緩衝區非空事件 | A開始給管道寫數據 | 管道是非空的 | 內核通知B可以進行讀取數據了。B解除阻塞,醒來 |
緩衝區滿事件 | A阻塞, | 管道滿了 | B沒有讀取數據 |
緩衝區非滿事件 | A開始給管道寫數據 | 管道非滿了 | B進行了讀取數據 |
阻塞模式缺點:一個線程只能處理一個I/o模式。如果要同時處理多個流,要麼多進程,要麼多線程。但是這兩個方法效率都不高。
非阻塞忙輪詢:不停地把所有流從頭到尾問一遍,又從頭開始,這樣就可以處理多個流了。
缺點是:如果所有的流中都沒有數據,只會白白浪費CPU,
非阻塞無差別輪詢:引進一個代理。代理同時觀察許多流的I/O事件,在空閒的時候,會把當前的線程阻塞掉,當有一個或者多個流有I/o操作事件時,就喚醒線程,於是就輪詢一遍所有的流。
沒有I/o事件時,程序阻塞到代理select處,我們僅僅從select處知道了有I/O事件發生了,但是不知道是哪個流,只能無差別輪詢。當流多的時候,效率也是相當的低了。
非阻塞事件輪詢:代理eventpool會將哪個流發生了什麼I/O事件通知我們。此時對這些流的操作都是有意思的,複雜度降到了n(1)
epoll是對select,poll模型的改進,提高了網絡編程的性能,廣泛用於大規模併發請求的c/s結構中。
1 觸發方式:
邊緣觸發/水平觸發,(只使用unix和linux操作系統)
2 原理
3 步驟
1 創建一個epoll對象
2 告訴epoll對象,在指定的socket上監聽指定的事件
3 詢問epoll對象,從上次查詢以來,那些socket發生了那些指定的事件
4 在這些socket上執行一些操作,
5 告訴epoll對象,修改socket列表和或者事件,並監控
6 重複 3 -5 ,直到完成
7 銷燬epoll對象
4 相關用法:
1 導入select模塊
import select
2 創建epoll對象
epoll=select.epoll()
3 註冊要監控的文件句柄和事件
epoll.register(文件句柄,事件類型)
事件類型:
select.EPOLLIN 可讀事件
select.EPOLLOUT 可寫事件
select.EPOLLERR 錯誤事件
select.EPOLLHUP 客戶端斷開事件
4 epoll.unregister(文件句柄) 銷燬文件句柄
5 epoll.fileno() 返回epoll的控制文件描述符
6 epoll.modify(fileno,event) fileno 是文件描述符,event是事件類型,作用是修改文件描述符,所對應的事件
7 epoll.fromfd(fileno) 從一個文件描述符,創建一個epoll對象
8 epoll.close() 關閉epoll對象的控制文件描述符