深入理解異步I/O+epoll+協程
前言
同步和異步的概念描述的是用戶線程與內核的交互方式:同步是指用戶線程發起IO請求後需要等待或者輪詢內核IO操作完成後才能繼續執行;而異步是指用戶線程發起IO請求後仍繼續執行,當內核IO操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。
阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式:阻塞是指IO操作需要徹底完成後才返回到用戶空間;而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成。
異步I/O
在理解異步I/O之前,我們先要知道什麽是同步I/O
在阻塞同步I/O模型下,用戶線程向內核發起 recvfrom 系統調用,當數據沒有準備好的時候,用戶線程阻塞。
而在異步I/O模式下,用戶線程在數據還沒有準備好的時候既不阻塞也不反復查詢,而是繼續幹自己該幹的事情。內核會開啟一個內核線程去讀取數據,等到數據準備好了,內核給用戶線程一個信號,用戶線程中斷去執行信號處理函數。
Paste_Image.pngnode.js中的異步回調也是采用開線程的方式實現的
epoll
epoll/select 是一種I/O多路復用模型
用戶線程可以先通過 epoll 註冊多個I/O事件
然後用戶線程反復執行調用 epoll/select 查詢是否有準備好的事件
如果有準備好的I/O事件則進行處理
關鍵點是一個用戶線程處理多個I/O事件
epoll/select 的區別在於
select 的底層原理是遍歷所有註冊的I/O事件,找出準備好的的I/O事件。
而 epoll 則是由內核主動通知哪些I/O事件需要處理,不需要用戶線程主動去反復查詢,因此大大提高了事件處理的效率。
協程
協程是一種輕量級的線程
本質上協程就是用戶空間下的線程
如果把線程/進程當作虛擬“CPU”,協程即跑在這個“CPU”上的線程。
協程的特點
- 占用的資源更少。
- 所有的切換和調度都發生在用戶態。
不管是進程還是線程,每次阻塞、切換都需要陷入系統調用,先讓CPU跑操作系統的調度程序,然後再由調度程序決定該跑哪一個線程。而且由於搶占式調度執行順序無法確定的特點,使用線程時需要非常小心地處理同步問題,而協程完全不存在這個問題。
因為協程可以在用戶態顯示控制切換
例子
傳統的生產者-消費者模型是一個線程寫消息,一個線程取消息,通過鎖機制控制隊列和等待,但一不小心就可能死鎖。
如果改用協程,生產者生產消息後,直接通過yield跳轉到消費者開始執行,待消費者執行完畢後,切換回生產者繼續生產,效率極高:
import time
def consumer():
r = ‘‘
while True:
n = yield r
if not n:
return
print(‘[CONSUMER] Consuming %s...‘ % n)
time.sleep(1)
r = ‘200 OK‘
def produce(c):
c.next()
n = 0
while n < 5:
n = n + 1
print(‘[PRODUCER] Producing %s...‘ % n)
r = c.send(n)
print(‘[PRODUCER] Consumer return: %s‘ % r)
c.close()
if __name__==‘__main__‘:
c = consumer()
produce(c)
協程的優點是可以用同步的處理方式實現異步回調的性能
深入理解異步I/O+epoll+協程