今天之所以這么早結束,主要是因為自己腦子不夠用了,發現最近的定義有點多,完全搞不清楚了,打算早點睡覺,今天的內容估計要引用很多別人的部分了。
看到題目的四個東東是不是驚呆了,我也是驚呆了,同時腦子還跟不上。
同步(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操作,而任務可能在兩次輪詢之間的任意時間完成。這會導致整體數據吞吐量的降低。
今天就是這些了,早點休息,累累累。。。
然后最近的博客有點水,明天最好加個餐。。。