日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

python 并發編程 非阻塞IO模型原理解析

系統 1809 0

非阻塞IO(non-blocking IO)

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

python 并發編程 非阻塞IO模型原理解析_第1張圖片

從圖中可以看出,當用戶進程發出read操作時,如果kernel中的數據還沒有準備好,那么它并不會block用戶進程,而是立刻返回一個error。從用戶進程角度講 ,它發起一個read操作后,并不需要等待,而是馬上就得到了一個結果。用戶進程判斷結果是一個error時,它就知道數據還沒有準備好,于是用戶就可以在本次到下次再發起read詢問的時間間隔內做其他事情,或者直接再次發送read操作。一旦kernel中的數據準備好了,并且又再次收到了用戶進程的system call,那么它馬上就將數據拷貝到了用戶內存(這一階段仍然是阻塞的,這段是本地拷貝,copy data ),然后返回。

也就是說非阻塞的recvform系統調用調用之后,進程并沒有被阻塞,內核馬上返回給進程,如果數據還沒準備好,
此時會返回一個error。進程在返回之后,可以干點別的事情,然后再發起recvform系統調用。重復上面的過程,
循環往復的進行recvform系統調用。這個過程通常被稱之為輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,
進行數據處理。需要注意,拷貝數據整個過程,進程仍然是屬于阻塞的狀態。
所以,在非阻塞式IO中,用戶進程其實是需要不斷的主動詢問kernel操作系統內存 數據準備好了沒有。

非阻塞IO示例

  • 設置socket接口為 非阻塞IO接口
  • 默認是True 為阻塞
  • server.setblocking(False)
  • 處理一下這個異常

BlockingIOError: [WinError 10035] 無法立即完成一個非阻止性套接字操作。

            
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 設置socket接口為 非阻塞IO接口
# 默認是True 為阻塞
server.setblocking(False)
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    print(addr)

  except BlockingIOError:
    print("干其他的工作")
server.close()
          

執行結果,如上面的圖,一直返回error消息

            
starting...
干其他的工作
干其他的工作
干其他的工作
干其他的工作
          

服務端 可以與 多個客戶端建立連接,實現服務端可以不停的建立連接

            
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 設置socket接口為 非阻塞IO接口
# 默認是True 為阻塞
server.setblocking(False)
r_list = []
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    r_list.append(conn)
    print(r_list)
  except BlockingIOError:
    pass
server.close()
          

起三個客戶端與服務端建立連接

python 并發編程 非阻塞IO模型原理解析_第2張圖片

r_list 存著所有建立的連接

有連接來,就建立連接,沒有連接來,就拋出異常

實現IO非阻塞 并發 多個連接

            
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 設置socket接口為 非阻塞IO接口
# 默認是True 為阻塞
server.setblocking(False)
r_list = []
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    r_list.append(conn)
    print(r_list)
  except BlockingIOError:
    # 定義刪除連接列表
    del_rlist = []
    for conn in r_list:
      try:
        data = conn.recv(1024)
        # 收空數據時候
        if not data:
          del_rlist.append(conn)
          continue
        conn.send(data.upper())
      # 沒有連接,拋出異常,就結束這次循環,繼續
      except BlockingIOError:
        continue
      # 套接字出現異常,客戶端單方面連接斷開
      except Exception:
        conn.close()
        del_rlist.append(conn)
        break
    # 結束上面循環之后,循環del_list 連接元素 刪除連接
    for conn in del_rlist:
      del_rlist.remove(conn)
server.close()
          

BUG:send也是IO阻塞接口

當send在數據量過大時候,也會阻塞。

send操作是,把應用程序把數據發送到操作系統緩存區里,而操作系統緩存區空間也是有限的。緩存區也會滿了,后面還有數據需要發送,那只能等緩存區清掉數據,有空間了,才能發送數據。所以在這里緩存區滿了,就阻塞。

修改后服務端的代碼 可以自己檢測IO,遇到IO切換單個線程的其他任務,去運行,實現單線程并發

            
from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 設置socket接口為 非阻塞IO接口
# 默認是True 為阻塞
server.setblocking(False)
r_list = []
w_list = []
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    r_list.append(conn)
    print(r_list)
  except BlockingIOError:
    # 收消息
    # 定義刪除連接列表
    del_rlist = []
    for conn in r_list:
      try:
        data = conn.recv(1024)
        # 收空數據時候
        if not data:
          del_rlist.append(conn)
          continue
        '''加入元祖 元祖有兩個元素 
        1.存放套接字連接
        2.準備要發送的的數據
        '''
        w_list.append((conn, data.upper()))
      # 沒有連接,拋出異常,就結束這次循環,繼續
      except BlockingIOError:
        continue
      # 套接字出現異常,客戶端單方面連接斷開
      except Exception:
        conn.close()
        del_rlist.append(conn)
        break
    # 發消息
    # 用于 發成功數據后,刪除套接字連接的列表
    del_wlist = []
        for item in w_list:
     try:
        conn = item[0]
        data = item[1]
        conn.send(data)
        # 發成功后,從列表刪除連接
        del_wlist.append(item)
      # send 有可能出現異常 沒發完情況
      except BlockingIOError:
        pass
    # 結束上面循環之后,循環del_wlist 連接元素 刪除連接
    for item in del_wlist:
      del_wlist.remove(item)
    # 結束上面循環之后,循環del_rlist 連接元素 刪除連接
    for conn in del_rlist:
      del_rlist.remove(conn)
server.close()
          

這就是非阻塞IO

但是非阻塞IO模型絕不被推薦。
我們不能否則其優點:能夠在等待任務完成的時間里干其他活了(包括提交其他任務,也就是 “后臺” 可以有多個任務在“”同時“”執行)。

干其他活時候,有可能來新的連接,新的連接來了,不能及時響應與該新的連接,建立連接。所以會導致問題:數據不會及時響應

但是也難掩其缺點:

1. 循環調用recv()將大幅度推高CPU占用率;這也是我們在代碼中留一句time.sleep(2)的原因,否則在低配主機下極容易出現卡機情況

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

3.死循環While True會導致CPU的無用的耗用、占用

此外,在這個方案中recv()更多的是起到檢測“操作是否完成”的作用,實際操作系統提供了更為高效的檢測“操作是否完成“作用的接口,例如select()多路復用模式,可以一次檢測多個連接是否活躍

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 宁晋县| 通城县| 若羌县| 天气| 清流县| 定日县| 阆中市| 木里| 鄂州市| 盐源县| 肇源县| 木兰县| 曲麻莱县| 满城县| 湖南省| 浪卡子县| 怀远县| 江门市| 新乐市| 吕梁市| 衡南县| 琼海市| 临泽县| 广河县| 房山区| 固安县| 临汾市| 保定市| 阿拉善盟| 邹城市| 通化县| 界首市| 临汾市| 南郑县| 思南县| 蚌埠市| 临桂县| 岳阳县| 砚山县| 石楼县| 镇平县|