python 并發(fā)編程 阻塞IO模型原理解析
阻塞IO(blocking IO)
在linux中,默認情況下所有的socket都是blocking,一個典型的讀操作流程大概是這樣:
當用戶進程調(diào)用了recvfrom這個系統(tǒng)調(diào)用,kernel內(nèi)核就開始了IO的第一個階段:準備數(shù)據(jù)。對于network io( 網(wǎng)絡(luò)io )來說,很多時候數(shù)據(jù)在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel( 內(nèi)核 )就要等待足夠的數(shù)據(jù)到來。
等著對方把數(shù)據(jù)放到自己操作系統(tǒng)內(nèi)存
而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數(shù)據(jù)準備好了,它就會將數(shù)據(jù)從kernel操作系統(tǒng)緩存中拷貝到用戶應(yīng)用程序內(nèi)存,
然后kernel返回結(jié)果,用戶進程才解除block的狀態(tài),重新運行起來。
這就是阻塞IO
所以,blocking IO的特點就是在IO執(zhí)行的兩個階段(等待數(shù)據(jù)和拷貝數(shù)據(jù)兩個階段)都被block了
網(wǎng)絡(luò)編程都是從listen\(\)、send\(\)、recv\(\) 等接口開始的,
使用這些接口可以很方便的構(gòu)建服務(wù)器/客戶機的模型。然而大部分的socket接口都是阻塞型的。如下圖ps:
所謂阻塞型接口是指系統(tǒng)調(diào)用(一般是IO接口)不返回調(diào)用結(jié)果并讓當前線程一直阻塞
只有當該系統(tǒng)調(diào)用獲得結(jié)果或者超時出錯時才返回。
服務(wù)端:
from socket import * server = socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8000)) server.listen(5) while True: print("starting...") conn,addr = server.accept() print(addr) while True: try: data = conn.recv(1024) if not data:break conn.send(data.upper()) except ConnectionResetError: break server.close()
客戶端
from socket import * client = socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8000)) 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()
實際上,除非特別指定,幾乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。這給網(wǎng)絡(luò)編程帶來了一個很大的問題,如在調(diào)用recv(1024)的同時,線程將被阻塞,在此期間,線程將無法執(zhí)行任何運算或響應(yīng)任何的網(wǎng)絡(luò)請求。
一個簡單的解決方案:
在服務(wù)器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進程),
這樣任何一個連接的阻塞都不會影響其他的連接。
該方案的問題是 :
開啟多進程或都線程的方式,在遇到要同時響應(yīng)成百上千路的連接請求,則無論多線程還是多進程都會嚴重占據(jù)系統(tǒng)資源,
降低系統(tǒng)對外界響應(yīng)效率,而且線程與進程本身也更容易進入假死狀態(tài)。
隨著客戶端數(shù)量增多,無限制的開線程,開銷非常大
不能解決阻塞IO問題 ,解決思路:起多線程
改進方案:
使用“線程池”或“連接池”?!熬€程池”旨在減少創(chuàng)建和銷毀線程的頻率,
其維持一定合理數(shù)量的線程,并讓空閑的線程重新承擔新的執(zhí)行任務(wù)?!斑B接池”維持連接的緩存池,盡量重用已有的連接、
減少創(chuàng)建和關(guān)閉連接的頻率。這兩種技術(shù)都可以很好的降低系統(tǒng)開銷,都被廣泛應(yīng)用很多大型系統(tǒng),如websphere、tomcat和各種數(shù)據(jù)庫等。
改進后方案其實也存在著問題:
“線程池”和“連接池”技術(shù)也只是在一定程度上緩解了頻繁調(diào)用IO接口帶來的資源占用。而且,所謂“池”始終是有限,
當請求大大超過上限時,“池”構(gòu)成的系統(tǒng)對外界的響應(yīng)并不比沒有池的時候效果好多少。所以使用“池”必須考慮其面臨的響應(yīng)規(guī)模,
并根據(jù)響應(yīng)規(guī)模調(diào)整“池”的大小。
線程池應(yīng)該隨著規(guī)模數(shù)調(diào)大,但是調(diào)大線程池,要在機器可承受范圍之內(nèi)。不能把線程池無限調(diào)大,這樣相當于無限開線程一樣,
多線程還是要用在規(guī)模比較小的情況
對應(yīng)上例中的所面臨的可能同時出現(xiàn)的上千甚至上萬次的客戶端請求,“線程池”或“連接池”或許可以緩解部分壓力,但是不能解決所有問題。總之,多線程模型可以方便高效的解決小規(guī)模的服務(wù)請求,但面對大規(guī)模的服務(wù)請求,多線程模型也會遇到瓶頸,可以用非阻塞接口來嘗試解決這個問題。
總結(jié):
始綜沒有解決單線程遇到IO問題,單線程遇到IO,就阻塞,用的是阻塞IO模型。
阻塞IO模型就是遇到IO阻塞不處理,就在原地等著。
應(yīng)該:
監(jiān)測單線程IO,遇到IO了,這個線程不要阻塞。直接切換到另外一個線程運行,這樣單線程效率就非常高了。
要解決的問題是:
單線程IO問題
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python cv2截取不規(guī)則區(qū)域圖片實例
今天小編就為大家分享一篇python cv2截取不規(guī)則區(qū)域圖片實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12python實現(xiàn)n個數(shù)中選出m個數(shù)的方法
今天小編就為大家分享一篇python實現(xiàn)n個數(shù)中選出m個數(shù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11python 實現(xiàn)對文件夾內(nèi)的文件排序編號
下面小編就為大家分享一篇python 實現(xiàn)對文件夾內(nèi)的文件排序編號,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04