python之IO併發-阻塞IO 非阻塞IO IO多路複用 非同步IO(協程)

hihibig發表於2024-12-09

阻塞IO即為之前正常使用的IO 邏輯簡單 非阻塞IO 可以把阻塞IO 設定為非阻塞IO,例如sockfd.setblocking(false)。如果設定成了非阻塞,無客戶端連線時就會報BlockingIOError錯誤,透過try來捕獲。透過迴圈來接受客戶端連線

還可以設定超時檢測,settimeout---sockfd.settimeout(5)超時報錯

while True:
    print("Waiting from connect...")
    try:
        connfd,addr = sockfd.accept()
    except (BlockingIOError,timeout) as e:
        sleep(2)
        f.write("%s : %s\n"%(ctime(),e))
        f.flush()
    else:
        print("Connect from",addr)
        data = connfd.recv(1024).decode()
        print(data)

IO多路複用:select poll epoll

elect適用於window,Linux ,unix,poll:inux unix epoll:unix

select最多監控1024個IO

poll比select監控的IO數量多

epoll比select poll的效率高,監控的IO多,觸發方式多

select:把要監測的IO放入rlist,wlist,xlist中,rlist適用於被動接受,wlist適用於主動寫入,xlist出現錯誤出發(一般不用)。select()函式返回值也是3個列表,迴圈3個列表處理IO就緒事件

from select import select
from socket import *
s=socket()
s.bind(('0.0.0.0',8000))
s.listen(5)
 
rlist=[s]
wlist=[]
xlist=[]
while True:
    rs,ws,xs=select(rlist,wlist,xlist)
    for r in rs:
        if r==s:
            conf, addr = r.accept()
            print('監聽到',addr,'的連結')
            rlist.append(conf)
        else:
            data=r.recv(1024)
            print(data)
            r.send(b'ok')
            if not data:
                r.close()
                rlist.remove(r)
    for w in ws:
        pass

poll:from select import poll

用到的函式有:p=poll()(建立poll物件),p.register(IO物件,POLLIN|POLLERR)(註冊IO事件),p.unregister(IO物件或檔案描述符) (取消註冊)“event & POLLIN” 按位與檢查是否檢測POLLIN事件(讀IO事件)。events=p.poll()(阻塞等待監控IO事件發生)events為列表裡面套元組[(fileno,event),()] 要自己做fileno和IO物件的對映字典,動態維護字典

from select import *
from socket import *
s=socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8887))
s.listen(3)
p=poll()
fdmap={s.fileno():s}
p.register(s,POLLIN)
while True:
    events=p.poll()
    for fileno,event in events:
        if fileno==s.fileno():
            conf,addr=s.accept()
            p.register(conf,POLLIN|POLLERR)
            fdmap[conf.fileno()]=conf
        elif event & POLLIN:
            data=fdmap[fileno].recv(1024).decode()
            print(data)
            if not data:
            #客戶端退出,unregister,字典刪除,套接字關閉
                p.unregister(fileno)
                fdmap[fileno].close()
                del fdmap[fileno]
                continue
            fdmap[fileno].send(b'ok')

epoll使用方法和poll一樣,要加E

協程

asyncio模組

普通函式def之前加async關鍵字 async def get_request()...
函式呼叫生成協程物件c=get_request()
建立任務物件 task=asyncio.ensure_future(c),多工則需建立tasks列表,把task append到tasks裡。loop.run_until_complete(asyncio.wait(tasks))
建立事件迴圈物件loop=asyncio.get_event_loop()
將任務物件裝載在事件迴圈物件中啟動事件迴圈物件loop.run_until_complete(task)
如果函式有返回值,則給任務物件繫結回撥函式task.add_done_callback(task_callback)
回撥函式必須有一個引數,data=引數.result(),data則為函式的返回值
await關鍵字:掛起發⽣阻塞操作的任務物件。 在任務物件表示的操作中,凡是阻塞操作的前⾯ 都必須加上await關鍵字進⾏修飾
如果用協程去獲取網路請求就需要用到:aiohttp模組,aiohttp支援非同步模組

在每⼀個with前加上async關鍵字

在阻塞操作前加上await關鍵字

async def get_request(url):
#requests是不⽀持非同步的模組
# response = await requests.get(url=url)
# page_text = response.text
#建立請求物件(sess)
    async with aiohttp.ClientSession() as sess:
#基於請求物件發起請求
#此處的get是發起get請求,常⽤引數:url,headers,params,proxy
#post⽅法發起post請求,常⽤引數:url,headers,data,proxy
#發現處理代理的引數和requests不⼀樣(注意),此處處理代理使⽤proxy='http://ip:port'
多工非同步爬⾍的完整程式碼實現:
    async with await sess.get(url=url) as response:
        page_text = await response.text()
#text():獲取字串形式的響應資料
#read():獲取⼆進位制形式的響應資料
    return page_text

第三方庫模組:gevent模組

用到的函式:f=gevent.spawn(func,argv)(生成協程函式)gevent.joinall(list,[timeout])

匯入monkey模組 from gevent import monkey
執行相應指令碼 monkey.patch_socket()(很多指令碼,需要自己選擇自己需要的,或者patch_all())
指令碼執行要在相應的模組匯入之前

"""
gevent server 基於協成的tcp併發
思路 : 1. 每個客戶函式端設定為協成
      2. 將socket模組下的阻塞變為可以觸發協程跳轉
"""
import gevent
from gevent import monkey
monkey.patch_all() # 執行指令碼,修改socket
from socket import *
 
def handle(c):
    while True:
        data = c.recv(1024).decode()
        if not data:
            break
        print(data)
        c.send(b'OK')
    c.close()
 
# 建立tcp套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
 
# 迴圈接收來自客戶端連線
while True:
    c,addr = s.accept()
    print("Connect from",addr)
    # handle(c) # 處理具體客戶端請求
    gevent.spawn(handle,c) # 協程方案

人工智慧(PythonNet)—— 目錄彙總

相關文章