1. 程式人生 > >深入理解異步I/O+epoll+協程

深入理解異步I/O+epoll+協程

事件 ont key 消息 完成 mat recv package orm

前言

同步和異步的概念描述的是用戶線程與內核的交互方式:同步是指用戶線程發起IO請求後需要等待或者輪詢內核IO操作完成後才能繼續執行;而異步是指用戶線程發起IO請求後仍繼續執行,當內核IO操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。
阻塞和非阻塞的概念描述的是用戶線程調用內核IO操作的方式:阻塞是指IO操作需要徹底完成後才返回到用戶空間;而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成。

異步I/O

在理解異步I/O之前,我們先要知道什麽是同步I/O
阻塞同步I/O模型下,用戶線程向內核發起 recvfrom 系統調用,當數據沒有準備好的時候,用戶線程阻塞。

此外還有一種非阻塞同步I/O,此時用戶線程不阻塞於 recvfrom,而是反復向系統查詢數據狀態。當數據準備好了,就對數據進行後續處理。

技術分享圖片 Paste_Image.png

而在異步I/O模式下,用戶線程在數據還沒有準備好的時候既不阻塞也不反復查詢,而是繼續幹自己該幹的事情。內核會開啟一個內核線程去讀取數據,等到數據準備好了,內核給用戶線程一個信號,用戶線程中斷去執行信號處理函數。

技術分享圖片 Paste_Image.png

node.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事件需要處理,不需要用戶線程主動去反復查詢,因此大大提高了事件處理的效率。

技術分享圖片 Paste_Image.png

協程

協程是一種輕量級的線程
本質上協程就是用戶空間下的線程
如果把線程/進程當作虛擬“CPU”,協程即跑在這個“CPU”上的線程。

協程的特點

  1. 占用的資源更少。
  2. 所有的切換和調度都發生在用戶態。

不管是進程還是線程,每次阻塞、切換都需要陷入系統調用,先讓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+協程