Python學習第54天(阻塞(blocking) IO和非阻塞(non-blocking)IO)


  今天之所以這么早結束,主要是因為自己腦子不夠用了,發現最近的定義有點多,完全搞不清楚了,打算早點睡覺,今天的內容估計要引用很多別人的部分了。

  看到題目的四個東東是不是驚呆了,我也是驚呆了,同時腦子還跟不上。

  同步(synchronous) IO和異步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分別是什么,到底有什么區別?這個問題其實不同的人給出的答案都可能不同,比如wiki,就認為asynchronous IO和non-blocking IO是一個東西。這其實是因為不同的人的知識背景不同,並且在討論這個問題的時候上下文(context)也不相同。所以,為了更好的回答這個問題,我先限定一下本文的上下文。

  本文討論的背景是Linux環境下的network IO。 

  Stevens在文章中一共比較了五種IO Model:

     blocking IO   

    nonblocking IO   

    IO multiplexing   

    signal driven IO   

    asynchronous IO

  由於signal driven IO在實際中並不常用,所以我這只提及剩下的四種IO Model。
  再說一下IO發生時涉及的對象和步驟。
      對於一個network IO (這里我們以read舉例),它會涉及到兩個系統對象,一個是調用這個IO的process (or thread),另一個就是系統內核(kernel)。當一個read操作發生時,它會經歷兩個階段:
   1 等待數據准備 (Waiting for the data to be ready)
   2 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)記住這兩點很重要,因為這些IO Model的區別就是在兩個階段上各有不同的情況。

其實我今天只看了阻塞IO和非阻塞IO,定義太繞口,知道是這么會事情,但是卻不知道怎么跟定義有機的對應

  

  一、 blocking IO (阻塞IO)  

  在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:

      當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:准備數據。對於network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據准備好了,它就會將數據從kernel中拷貝到用戶內存,然后kernel返回結果,用戶進程才解除block的狀態,重新運行起來。
  所以,blocking IO的特點就是在IO執行的兩個階段都被block了。

 

  對應實例的意思大致就是client不連接server端,server就會在accept那個位置一直等着:  

import socket
# 客戶端
sk=socket.socket()
sk.connect(("127.0.0.1",8080))

while 1:
    data=sk.recv(1024)
    print(data.decode("utf8"))
    sk.send(b"hello server")

#服務端
import socket

sk=socket.socket()
sk.bind(("127.0.0.1",8080))
sk.listen(5)

while 1:
    conn,addr=sk.accept()
    while 1:
        conn.send("hello client".encode("utf8"))
        data=conn.recv(1024)
        print(data.decode("utf8"))

  

  二、non-blocking IO(非阻塞IO)

  linux下,可以通過設置socket使其變為non-blocking。當對一個non-blocking socket執行讀操作時,流程是這個樣子:

      從圖中可以看出,當用戶進程發出read操作時,如果kernel中的數據還沒有准備好,那么它並不會block用戶進程,而是立刻返回一個error。從用戶進程角度講 ,它發起一個read操作后,並不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有准備好,於是它可以再次發送read操作。一旦kernel中的數據准備好了,並且又再次收到了用戶進程的system call,那么它馬上就將數據拷貝到了用戶內存,然后返回。


  所以,用戶進程其實是需要不斷的主動詢問kernel數據好了沒有。

 注意:

      在網絡IO時候,非阻塞IO也會進行recvform系統調用,檢查數據是否准備好,與阻塞IO不一樣,”非阻塞將大的整片時間的阻塞分成N多的小的阻塞, 所以進程不斷地有機會 ‘被’ CPU光顧”。即每次recvform系統調用之間,cpu的權限還在進程手中,這段時間是可以做其他事情的,

      也就是說非阻塞的recvform系統調用調用之后,進程並沒有被阻塞,內核馬上返回給進程,如果數據還沒准備好,此時會返回一個error。進程在返回之后,可以干點別的事情,然后再發起recvform系統調用。重復上面的過程,循環往復的進行recvform系統調用。這個過程通常被稱之為輪詢。輪詢檢查內核數據,直到數據准備好,再拷貝數據到進程,進行數據處理。需要注意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態。

 

  說的大致意思就是我們把setblocking()里面的參數由原來默認True更改為Flase,當沒有接收的時候我們會每隔一段時間去看是否收到,從而是空置過程的程序歸我們使用。

  看范例:

import time

#服務端
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt
sk.bind(('127.0.0.1',6667))
sk.listen(5)
sk.setblocking(False)
while True:
    try:
        print ('waiting client connection .......')
        connection,address = sk.accept()   # 進程主動輪詢
        print("+++",address)
        client_messge = connection.recv(1024)
        print(str(client_messge,'utf8'))
        connection.close()
    except Exception as e:
        print (e)
        time.sleep(4)

#客戶端
import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

while True:
    sk.connect(('127.0.0.1',6667))
    print("hello")
    sk.sendall(bytes("hello","utf8"))
    time.sleep(2)
    break

  優缺點非常明顯:

  優點:能夠在等待任務完成的時間里干其他活了(包括提交其他任務,也就是 “后台” 可以有多個任務在同時執行)。

  缺點:任務完成的響應延遲增大了,因為每過一段時間才去輪詢一次read操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體數據吞吐量的降低。

 

今天就是這些了,早點休息,累累累。。。

然后最近的博客有點水,明天最好加個餐。。。

 

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM