標籤:影響 websphere HERE except 結果 stream recv 改進 編程
阻塞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 Threaddef 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()
python並發編程:阻塞IO