python並發編程:阻塞IO


阻塞IO(blocking  IO)

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

  當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:准備數據。對於network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的udp包),這個時候kernel就要等待足夠的數據到來

而在用戶進程這邊,整個進程會被阻塞,當kernel一直等到數據准備好了,它就會將數據從kernel中拷貝到用戶內存
然后kernel返回結果,用戶進程才解除block的狀態,重新運行起來

  所以,blocking IO的特點就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了

幾乎所有的程序員第一次接觸到的網絡編程都是從listen(),send(),recv()等接口開

始的,使用這些接口可以很方便的構建服務器/客戶機的模型。然而大部分的socket接口都是

阻塞型的。如下圖

ps:
所謂阻塞型接口是指系統調用(一般是IO接口)不返回調用結果並讓當前線程一直阻塞

只有當該系統調用獲得結果或者超時出錯時才返回。

  

 

實際上,除非特別指定,幾乎所有的IO接口(包括socket接口)都是阻塞型的。這給網絡編程帶來了一個很大的問題。如在調用recv(1024)的同時,線程將被阻塞,在此期間,線程將無法執行任何運算或響應任何的網絡請求。

一個簡單的解決方案:

在服務端使用多線程(或多進程)。多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進程),這樣任何一個連接的阻塞都不會影響其他的連接。

  該方案的問題是:

開啟多進程或多線程的方式,在遇到同時響應成百上千的連接請求,則無論多線程還是多進程都會嚴重占據系統資源,降低系統對外界響應效率,而且線程與進程本身也更容易進入假死狀態

  改進方案:

很多程序員可能會考慮使用“線程池”或“連接池”。“線程池”旨在減少創建和銷毀線程的頻率,其維持一定合理數量的線程,並讓空閑的線程重新承擔新的執行任務。“連接池”維持連接的緩存池,盡量重用已有的連接,減少創建和關閉連接的頻率。這兩種技術都可以很好的降低系統開銷,都被廣泛應用很多
大型系統,如websphere、tomcat和各種數據庫等

  改進后方案其實也存在着問題:

“線程池”和“連接池”技術也只是在一定程度上緩解了頻繁調用IO接口帶來的資源占用。而且“池”始終尤其上限,當請求大大超過上限時,“池”構成的系統對外界的響應並不比沒有池
的時候效果好多少。
所以使用“池”必須考慮其面臨的響應規模,並根據規模調整“池”的大小

  對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,“線程池”或“連接池”或許可以緩解部分壓力,但是不能解決所有問題。總之,多線程模型可以方便高效的解決小規模的服務請求,但面對大規模的服務請求,多線程模型也會遇到瓶頸,可以用非阻塞接口來嘗試解決這個問題。

 

練習:

服務端:

from socket import *
from threading import Thread

def communicate(conn):
    while True:
        try:
            data = conn.recv(1024)
            if not data:
                break
            conn.send(data.upper())
        except ConnectionResetError:
            break
    conn.close()


server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    print('starting...')
    conn,addr = server.accept()
    print(addr)

    t = Thread(target=communicate, args=(conn,))
    t.start()

server.close()

  客戶端:

from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))

while True:
    msg = input("請輸入數據:").strip()
    if not msg:
        continue
    client.send(msg.encode('utf-8'))
    data = client.recv(1024)
    print(data.decode('utf-8'))

client.close()

  

  


免責聲明!

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



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