前言
同步和異步的概念描述的是用戶線程與內核的交互方式:同步是指用戶線程發(fā)起IO請求后需要等待或者輪詢內核IO操作完成后才能繼續(xù)執(zhí)行幕庐;而異步是指用戶線程發(fā)起IO請求后仍繼續(xù)執(zhí)行盼产,當內核IO操作完成后會通知用戶線程蝎土,或者調用用戶線程注冊的回調函數(shù)。
阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式:阻塞是指IO操作需要徹底完成后才返回到用戶空間润绵;而非阻塞是指IO操作被調用后立即返回給用戶一個狀態(tài)值,無需等到IO操作徹底完成。
異步I/O
在理解異步I/O之前葡盗,我們先要知道什么是同步I/O
在阻塞同步I/O模型下,用戶線程向內核發(fā)起 recvfrom 系統(tǒng)調用啡浊,當數(shù)據沒有準備好的時候觅够,用戶線程阻塞。
此外還有一種非阻塞同步I/O巷嚣,此時用戶線程不阻塞于 recvfrom喘先,而是反復向系統(tǒng)查詢數(shù)據狀態(tài)。當數(shù)據準備好了廷粒,就對數(shù)據進行后續(xù)處理窘拯。
而在異步I/O模式下,用戶線程在數(shù)據還沒有準備好的時候既不阻塞也不反復查詢坝茎,而是繼續(xù)干自己該干的事情涤姊。內核會開啟一個內核線程去讀取數(shù)據,等到數(shù)據準備好了嗤放,內核給用戶線程一個信號思喊,用戶線程中斷去執(zhí)行信號處理函數(shù)。
node.js中的異步回調也是采用開線程的方式實現(xiàn)的
epoll
epoll/select 是一種I/O多路復用模型
用戶線程可以先通過 epoll 注冊多個I/O事件
然后用戶線程反復執(zhí)行調用 epoll/select 查詢是否有準備好的事件
如果有準備好的I/O事件則進行處理
關鍵點是一個用戶線程處理多個I/O事件
epoll/select 的區(qū)別在于
select 的底層原理是遍歷所有注冊的I/O事件次酌,找出準備好的的I/O事件恨课。
而 epoll 則是由內核主動通知哪些I/O事件需要處理,不需要用戶線程主動去反復查詢和措,因此大大提高了事件處理的效率庄呈。
協(xié)程
協(xié)程是一種輕量級的線程
本質上協(xié)程就是用戶空間下的線程
如果把線程/進程當作虛擬“CPU”,協(xié)程即跑在這個“CPU”上的線程派阱。
協(xié)程的特點
- 占用的資源更少诬留。
- 所有的切換和調度都發(fā)生在用戶態(tài)。
不管是進程還是線程贫母,每次阻塞文兑、切換都需要陷入系統(tǒng)調用,先讓CPU跑操作系統(tǒng)的調度程序腺劣,然后再由調度程序決定該跑哪一個線程绿贞。而且由于搶占式調度執(zhí)行順序無法確定的特點,使用線程時需要非常小心地處理同步問題橘原,而協(xié)程完全不存在這個問題籍铁。
因為協(xié)程可以在用戶態(tài)顯示控制切換
例子
傳統(tǒng)的生產者-消費者模型是一個線程寫消息涡上,一個線程取消息,通過鎖機制控制隊列和等待拒名,但一不小心就可能死鎖吩愧。
如果改用協(xié)程,生產者生產消息后增显,直接通過yield跳轉到消費者開始執(zhí)行雁佳,待消費者執(zhí)行完畢后,切換回生產者繼續(xù)生產同云,效率極高:
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)
協(xié)程的優(yōu)點是可以用同步的處理方式實現(xiàn)異步回調的性能