Python實(shí)現(xiàn)異步IO的示例
前言
用阻塞 API 寫(xiě)同步代碼最簡(jiǎn)單,但一個(gè)線程同一時(shí)間只能處理一個(gè)請(qǐng)求,有限的線程數(shù)導(dǎo)致無(wú)法實(shí)現(xiàn)萬(wàn)級(jí)別的并發(fā)連接,過(guò)多的線程切換也搶走了 CPU 的時(shí)間,從而降低了每秒能夠處理的請(qǐng)求數(shù)量。為了達(dá)到高并發(fā),你可能會(huì)選擇一個(gè)異步框架,用非阻塞 API 把業(yè)務(wù)邏輯打亂到多個(gè)回調(diào)函數(shù),通過(guò)多路復(fù)用與事件循環(huán)的方式實(shí)現(xiàn)高并發(fā)。
磁盤 IO 為例,描述了多線程中使用阻塞方法讀磁盤,2 個(gè)線程間的切換方式。那么,怎么才能實(shí)現(xiàn)高并發(fā)呢?
把上圖中本來(lái)由內(nèi)核實(shí)現(xiàn)的請(qǐng)求切換工作,交由用戶態(tài)的代碼來(lái)完成就可以了,異步化編程通過(guò)應(yīng)用層代碼實(shí)現(xiàn)了請(qǐng)求切換,降低了切換成本和內(nèi)存占用空間。異步化依賴于 IO 多路復(fù)用機(jī)制,比如 Linux 的 epoll 或者 Windows 上的 iocp,同時(shí),必須把阻塞方法更改為非阻塞方法,才能避免內(nèi)核切換帶來(lái)的巨大消耗。Nginx、Redis 等高性能服務(wù)都依賴異步化實(shí)現(xiàn)了百萬(wàn)量級(jí)的并發(fā)。
下圖描述了異步 IO 的非阻塞讀和異步框架結(jié)合后,是如何切換請(qǐng)求的。
然而,寫(xiě)異步化代碼很容易出錯(cuò)。因?yàn)樗凶枞瘮?shù),都需要通過(guò)非阻塞的系統(tǒng)調(diào)用拆分成兩個(gè)函數(shù)。雖然這兩個(gè)函數(shù)共同完成一個(gè)功能,但調(diào)用方式卻不同。第一個(gè)函數(shù)由你顯式調(diào)用,第二個(gè)函數(shù)則由多路復(fù)用機(jī)制調(diào)用。
這種方式違反了軟件工程的內(nèi)聚性原則,函數(shù)間同步數(shù)據(jù)也更復(fù)雜。特別是條件分支眾多、涉及大量系統(tǒng)調(diào)用時(shí),異步化的改造工作會(huì)非常困難。
Python如何實(shí)現(xiàn)異步調(diào)用
from flask import Flask import time app = Flask(__name__) @app.route('/bar') def bar(): time.sleep(1) return '<h1>bar!</h1>' @app.route('/foo') def foo(): time.sleep(1) return '<h1>foo!</h1>' if __name__ == '__main__': app.run(host='127.0.0.1',port=5555,debug=True)
采用同步的方式調(diào)用
import requests import time starttime = time.time() print(requests.get('http://127.0.0.1:5555/bar').content) print(requests.get('http://127.0.0.1:5555/foo').content) print("消耗時(shí)間: ",time.time() -starttime)
b'<h1>bar!</h1>'
b'<h1>foo!</h1>'
消耗時(shí)間: 2.015509605407715
采樣異步的方式調(diào)用:
重點(diǎn):
1.將阻塞io改為非阻塞io;
2.多路復(fù)用io監(jiān)聽(tīng)內(nèi)核事件,事件觸發(fā)通過(guò)回調(diào)函數(shù);
3.用戶態(tài)代碼采取事件循環(huán)的方式獲取事件,執(zhí)行事件的回調(diào)函數(shù);
import selectors import socket import time # from asynrequest import ParserHttp class asynhttp: def __init__(self): self.selecter = selectors.DefaultSelector() def get(self,url,optiondict = None): global reqcount reqcount += 1 s = socket.socket() s.setblocking(False) try: s.connect(('127.0.0.1',5555)) except BlockingIOError: pass requset = 'GET %s HTTP/1.0\r\n\r\n' % url callback = lambda : self.send(s,requset) self.selecter.register(s.fileno(),selectors.EVENT_WRITE,callback) def send(self,s,requset): self.selecter.unregister(s.fileno()) s.send(requset.encode()) chunks = [] callback = lambda: self.recv(s,chunks) self.selecter.register(s.fileno(),selectors.EVENT_READ,callback) def recv(self,s,chunks): self.selecter.unregister(s.fileno()) chunk = s.recv(1024) if chunk: chunks.append(chunk) callback = lambda: self.recv(s,chunks) self.selecter.register(s.fileno(), selectors.EVENT_READ, callback) else: global reqcount reqcount -= 1 request_first,request_headers,request_content,_ = ParserHttp.parser(b''.join(chunks)) print("解析數(shù)據(jù):",request_first,request_headers,request_content) print((b''.join(chunks)).decode()) return (b''.join(chunks)).decode() starttime = time.time() reqcount = 0 asynhttper = asynhttp() asynhttper.get('/bar') asynhttper.get('/foo') while reqcount: events = asynhttper.selecter.select() for event,mask in events: func = event.data func() print("消耗時(shí)間:" ,time.time() - starttime)
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 13
Server: Werkzeug/1.0.1 Python/3.7.7
Date: Thu, 15 Oct 2020 03:28:16 GMT<h1>bar!</h1>
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 13
Server: Werkzeug/1.0.1 Python/3.7.7
Date: Thu, 15 Oct 2020 03:28:16 GMT<h1>foo!</h1>
消耗時(shí)間: 1.0127637386322021
以上就是Python實(shí)現(xiàn)異步IO的示例的詳細(xì)內(nèi)容,更多關(guān)于python 異步IO的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Django ValuesQuerySet轉(zhuǎn)json方式
這篇文章主要介紹了Django ValuesQuerySet轉(zhuǎn)json方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Python中的JSON?Pickle?Shelve模塊特性與區(qū)別實(shí)例探究
在Python中,處理數(shù)據(jù)序列化和持久化是極其重要的,JSON、Pickle和Shelve是三種常用的模塊,它們提供了不同的方法來(lái)處理數(shù)據(jù)的序列化和持久化,本文將深入研究這三個(gè)模塊,探討它們的特性、用法以及各自的優(yōu)缺點(diǎn)2024-01-01Python中Celery異步任務(wù)隊(duì)列的具體使用
Celery是一個(gè)用于處理分布式任務(wù)和作業(yè)隊(duì)列的異步任務(wù)隊(duì)列庫(kù),本文主要介紹了Python中Celery異步任務(wù)隊(duì)列的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02pytorch?tensor內(nèi)所有元素相乘實(shí)例
這篇文章主要介紹了pytorch?tensor內(nèi)所有元素相乘實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Python3 tkinter 實(shí)現(xiàn)文件讀取及保存功能
tkinter是一個(gè)跨平臺(tái)的GUI庫(kù),開(kāi)發(fā)的程序可以在win,linux或者mac下運(yùn)行,tkinter是python自帶的GUI庫(kù),是對(duì)圖形庫(kù)TK的封裝。本文通過(guò)實(shí)例代碼給大家介紹Python3 tkinter 實(shí)現(xiàn)文件讀取及保存功能,感興趣的朋友跟隨小編一起看看吧2019-09-09Python使用django搭建web開(kāi)發(fā)環(huán)境
這篇文章主要為大家詳細(xì)介紹了Python使用django搭建web開(kāi)發(fā)環(huán)境,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06