IO模型

一:IO簡介
Unix(like)中,一切皆文件。Socket、FIFO、管道、終端都是文件,一切都是流。在信息交換的過程當中,實際都是對這些流進行的數據收發操做,簡稱I/O操做(系統調用read、write)。而流有不少,因而就用文件描述符(fd)來區分具體是哪一個流。For example,咱們建立了一個socket,系統調用會返回一個fd,對socket的任何操做都是對這個fd的操做(隱隱包含着一種分層與抽象的思想)。web

二:同步異步、阻塞非阻塞
同步與異步是一種通訊機制,涉及到調用方和被調用方(針對應用程序與內核而言)。同步過程當中,進程觸發IO操做並等待(阻塞)或者輪詢的(非阻塞)去查看IO操做是否完成;異步過程當中,進程觸發IO操做之後,直接返回,作本身的事情,IO交給內核來處理,完成後內核通知進程IO完成。同步和異步關注的是程序之間的協做關係。同步分爲阻塞和非阻塞,異步則只有非阻塞。
阻塞和非阻塞是一種調用機制,只涉及到調用方(針對單個進程的執行狀態),關注的是IO操做的執行。調用方等待IO操做完成後返回則爲阻塞;調用方無需等待IO操做完成便返回則爲非阻塞,在非阻塞的狀況下,調用方經常須要主動去check,得到IO的操做結果。緩存

三:深刻下阻塞
由於一個線程只能處理一個socket的IO,若是想同時處理多個,能夠用非阻塞忙輪詢的方法,僞代碼是這樣的:app

for{
    for _,v := range []streams{
        if v has data
        read until unavailable
    }
}

把流(stream)從頭至尾讀一遍就能處理了,但是這樣效率很低,要是全部流都沒有IO事件,就浪費了CPU的資源。爲了不CPU空轉,不讓這個線程去檢查流是否有IO事件,而是引進一個代理(起初是select,後來是epoll),它能夠同時observe許多stream事件,若是沒有事件,代理就阻塞,線程就不會去挨個check了。僞代碼:異步

for{
    select([]streams) 
    for _,v := range []streams{
        if v has data
        read until unavailable
    }
}

可即使這樣,依舊要去作循環,由於select只是告訴線程有IO事件發生,可並無告訴線程是哪一個fd(或者多個),因此select的複雜度是O(n)。epoll(event poll)就解決了這個問題,因此epoll是事件驅動的,由於每一個事件上關聯了fd,複雜度也降到了O(1)。socket

for{
    happened_IO := append(happened_IO,epoll_wait(epollfd))
    for _,v := range happened_IO{
      read or write
    }
}

四:幾種IO模型
IO發生時(以network IO read爲例)涉及到兩個系統對象:一個是調用這個IO的process(進程)或者thread(線程),以及兩個階段:一、等待數據準備,二、將數據從內核copy到process中。 IO模型的區別就是在這兩個階段上的差別。async

1)blocking(阻塞)IO
當調用一個系統調用read時,kernel就開始IO的第一個階段:準備數據,對於network IO來講,不少時候數據一開始尚未到達(一個TCP包沒有接收完整),這個時候kernel就要等待足夠的數據到來(這也和緩存IO仍是非緩存IO有關,通常都是緩存IO)。而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,就會將數據從系統內存copy到用戶內存,而後kernel返回結果,用戶進程才接觸block狀態,從新運行起來。blocking IO的特色就是兩個階段都被block。線程

2)non-blocking(非阻塞)IO
當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從用戶進程角度來說,他發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。用戶進程判斷結果是error時,他就知道數據尚未準備好,因而就再次發送read調用。知道kernel中數據準備好了後,而且再次接收到system call後,將數據copy到用戶內存。可是這種模型效率很低,上文「深刻下阻塞」有提到。代理

3)multiplexing(多路複用)IO
其實就是select/epoll,也稱爲event driven IO。select/epoll的好處就是一個thread能夠同時處理多個socket的IO,其基本原理就是select/epoll會不斷輪詢所負責的socket,當某個socket有數據到達了,就通知用戶進程。當用戶進程調用了select/epoll,整個進程就會被block,同時kernel會observe全部select/epoll負責的socket,任何一個socket中數據準備好以後,select/epoll就會返回,這時用戶進程再調用read system call,將數據從kernel copy進用戶內存。
multiplexing io和blocking io差異不大,還更差一些,由於他有兩個system call,可是他的優點是他能夠處理多個connection。因此使用select/epoll的web server不必定處理速度很快,他只是能處理更多鏈接。code

4)asynchronous(異步)IO
用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。server

主要參考了Richard Stevens的「UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking 」的I/O Models

相關文章
相關標籤/搜索