python併發編程之IO模型 同步 異步 阻塞 非阻塞

IO淺談

首先web

咱們在談及IO模型的時候,就必需要引入一個「操做系統」級別的調度者-系統內核(kernel),而阻塞非阻塞是跟進程/線程嚴密相關的,而進程/線程又是依賴於操做系統存在的,因此天然不能脫離操做系統來討論阻塞非阻塞。同步/異步也是跟任務流相關的,因此要全面理解就必須考慮到併發的任務流,否則,確定很難舉出恰當的例子的。編程

 

本文討論的背景是Linux環境下的network IO。
本文最重要的參考文獻是Richard Stevens的「UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking 」,6.2節「I/O Models 」,Stevens在這節中詳細說明了各類IO的特色和區別,若是英文夠好的話,推薦直接閱讀。Stevens的文風是有名的深刻淺出,因此不用擔憂看不懂。本文中的流程圖也是截取自參考文獻。

網絡

 

在網絡併發編程中,通俗的講IO分爲兩個東西:一、等待 ==> 二、數據搬遷併發

或者這樣說:當IO發生時,會涉及到對象和步驟,當進行網絡IO(Network IO)時候,必須涉及到兩個系統對象,一個不可避免的就是產生這個IO的進程或者線程(process or thread),還有另一個老大級人物:系統內核(kernel),至於爲什麼要讓老大出場處理IO,究其緣由就是,好比咱們在進行讀操做的時候,進程或線程是沒法直接跟硬件打交道,沒法直接從硬盤中直接拿到咱們想要的數據,而是須要系統內核取幫咱們去取,它取須要過程,有過程咱們就得等待,系統內核取到了先會將數據放進它本身的內存空間中,咱們要取還得從它的內存空間把數據遷移到進程的內存空間,這纔算真正地拿到數據。異步

通俗的講就是:socket

當一個read操做發生時,會經歷兩個階段:async

1)等待數據準備(Waiting for the data to be ready)函數

2)將數據從內核拷貝到進程(線程所在的)的內存中(Copying the data from the kernel to the process or thread)性能

四個概念

同步 synchronous

一個進程或線程在提交任務或發出調用後,要等待返回的最終結果後纔算執行完畢,而後纔會繼續執行下一步操做spa

所謂同步,就是沒獲得最終結果以前,就不會繼續下一步操做。
這就比如單行道過收費站同樣,一個接着一個,只有上一個結束收費下一輛車才能開始進入站窗口交費。每輛車就至關與一個進程或線程。
其實絕大多數的函數都是同步調用,咱們都是拿到結果以後在進行下一步代碼操做。

而咱們在談及同步和異步的時候,特指那些須要其餘部件協做或者須要必定時間完成的任務。

異步 asynchronous

異步的概念與同步相對,當一個進程或線程發起一個異步功能調用或者說提交任務方式是異步提交的時,不會當即獲得結果。它會繼續執行下一步代碼。而異步功能完成後,經過狀態、通知或回調函數來通知調用者。

若是是經過狀態在通知,那麼調用者須要每隔一段時間檢查一次結果是否返回,效率很低。

若是使用通知或者回調的方式,則進程或線程就不須要再作額外的操做,效率很高。

阻塞 blocking

首先阻塞是一種狀態,一種進程或者線程的狀態,不要把阻塞與同步混淆。

也就是說當進程或線程發起調用或提交任務以後,結果返回以前,好比遇到IO操做,則調用者(該進程或線程)就會被掛起,而後只有等到有結果後纔會再將阻塞的線程激活。

非阻塞 non-blocking

非阻塞與阻塞相反,就算不能當即獲得結果也不會將其掛起,而是會繼續執行下一步。

四種常見IO模型 

 IO模型主要分五種

    blocking IO    阻塞IO
    nonblocking IO  非阻塞IO
    IO multiplexing  複用型IO
    signal driven IO  信號驅動型IO
    asynchronous IO  異步IO

他們直接的區別點在於過程、進程(或線程)的狀態、發起調用方式

首先最多見的IO模型:blocking IO

blocking IO

默認狀況下,全部的socket都是阻塞IO,一個典型的read操做流程

non-blocking IO

設置socket可使其變爲非阻塞,當對一個非阻塞的socket執行讀操做的時候,流程會不同:

從圖中能夠看出,當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,
而是馬上返回一個error。從用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。
用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。一旦kernel中的數據準備好了,
而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存,而後返回。 因此,用戶進程實際上是須要不斷的主動詢問kernel數據好了沒有。

IO multiplexing

IO multiplexing這個詞可能有點陌生,可是若是我說select,epoll,大概就都能明白了。有些地方也稱這種IO方式爲event driven IO。咱們都知道,select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select/epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。它的流程如圖:

當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。
這個圖和blocking IO的圖其實並無太大的不一樣,事實上,還更差一些。由於這裏須要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。可是,用select的優點在於它能夠同時處理多個connection。(多說一句。因此,若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。)
在IO multiplexing Model中,實際中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

Asynchronous I/O

 

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

小結

 blocking和non-blocking的區別

調用blocking IO會一直block住對應的進程直到操做完成,而non-blocking IO在內核準備數據這個階段,進程是不會非阻塞狀態。

synchronous IO和asynchronous IO的區別

同步IO在進行IO操做的時候會將進程阻塞,而異步IO卻不會。

因此blocking IO,non-blocking IO,IO multiplexing都屬於synchronous IO,而asynchronous IO則不同,當進程發起IO 操做以後,就直接返回不再理睬了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程當中,進程徹底沒有被block。

 最後各個IO Model的比較如圖所示:

 

 

 

 

 

 

 

 

———————————————— 
版權聲明:本文爲CSDN博主「historyasamirror」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處連接及本聲明。
原文連接:https://blog.csdn.net/historyasamirror/article/details/5778378

相關文章
相關標籤/搜索