python協(xié)程實(shí)現(xiàn)高并發(fā)的技術(shù)詳解
協(xié)程是實(shí)現(xiàn)高并發(fā)的一種非常高效的方式,特別適合處理大量I/O操作(如網(wǎng)絡(luò)請(qǐng)求、文件操作)的場(chǎng)景。它通過在單個(gè)線程內(nèi)實(shí)現(xiàn)多個(gè)任務(wù)的切換來避免阻塞,從而最大限度地利用CPU資源。
核心概念與簡(jiǎn)單示例
要理解協(xié)程如何實(shí)現(xiàn)高并發(fā),首先要明白幾個(gè)關(guān)鍵概念:
- 協(xié)程(Coroutine):一種輕量級(jí)的“微線程”,可以在執(zhí)行過程中暫停(await)并在適當(dāng)?shù)臅r(shí)候恢復(fù),而不是像普通函數(shù)那樣一次性執(zhí)行完畢。這種可掛起的能力是高并發(fā)的基石。
- 事件循環(huán)(Event Loop):這是協(xié)程的“大腦”或調(diào)度中心。它負(fù)責(zé)在單個(gè)線程中調(diào)度和執(zhí)行多個(gè)協(xié)程任務(wù)。當(dāng)一個(gè)協(xié)程因等待I/O(比如網(wǎng)絡(luò)響應(yīng))而暫停時(shí),事件循環(huán)會(huì)立刻切換到另一個(gè)就緒的協(xié)程去執(zhí)行,從而避免了CPU的空等。
- async/await 關(guān)鍵字:async 用于聲明一個(gè)函數(shù)是異步函數(shù)(即協(xié)程),await 用于在協(xié)程內(nèi)部掛起自身,直到其后面的異步操作完成。
下面是一個(gè)簡(jiǎn)單的例子,演示了如何使用 asyncio 庫(kù)創(chuàng)建兩個(gè)協(xié)程,讓它們“并發(fā)”地執(zhí)行:
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay) # 模擬一個(gè)耗時(shí)的I/O操作
print(what)
async def main():
print(f"程序開始于: {time.strftime('%X')}")
# 使用 asyncio.gather 來并發(fā)運(yùn)行兩個(gè)協(xié)程
await asyncio.gather(
say_after(2, 'Hello,'),
say_after(1, 'World!')
)
print(f"程序結(jié)束于: {time.strftime('%X')}")
# 運(yùn)行主協(xié)程
asyncio.run(main())
在這個(gè)例子中,雖然 Hello, 任務(wù)設(shè)定等待2秒,而 World! 任務(wù)只等待1秒,但由于它們是并發(fā)執(zhí)行的,總耗時(shí)大約只有2秒,而不是順序執(zhí)行時(shí)的3秒。輸出將會(huì)是:
程序開始于: 12:00:00
World!
Hello,
程序結(jié)束于: 12:00:02
高并發(fā)實(shí)踐:網(wǎng)絡(luò)請(qǐng)求
協(xié)程的真正威力體現(xiàn)在處理成千上萬個(gè)網(wǎng)絡(luò)請(qǐng)求時(shí)。下面的例子展示了如何使用 aiohttp 庫(kù)異步地并發(fā)獲取多個(gè)網(wǎng)頁(yè)內(nèi)容。
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
async with session.get(url) as response:
# 注意:在實(shí)際項(xiàng)目中,對(duì)于大響應(yīng)體,應(yīng)使用 response.content.read(chunk_size) 流式讀取
html = await response.text()
print(f"從 {url} 獲取了 {len(html)} 個(gè)字符")
async def main():
urls = [
'https://www.python.org',
'https://www.example.com',
'https://httpbin.org/get',
# ... 可以添加更多URL
]
# 關(guān)鍵:在整個(gè)抓取會(huì)話期間,復(fù)用同一個(gè) ClientSession 實(shí)例
# 其內(nèi)部維護(hù)了一個(gè)連接池,可以復(fù)用TCP連接,極大提升性能
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
# 為每個(gè)URL創(chuàng)建一個(gè)協(xié)程任務(wù)
task = asyncio.create_task(fetch_url(session, url))
tasks.append(task)
# 等待所有任務(wù)完成
await asyncio.gather(*tasks)
# 記錄耗時(shí)
start_time = time.time()
asyncio.run(main())
end_time = time.time()
print(f"總共耗時(shí): {end_time - start_time:.2f} 秒")
這個(gè)程序幾乎在同一時(shí)間發(fā)起所有請(qǐng)求,總耗時(shí)基本等于最慢的那個(gè)請(qǐng)求的耗時(shí),而不是所有請(qǐng)求耗時(shí)的總和。
協(xié)程如何實(shí)現(xiàn)高并發(fā):核心技術(shù)
協(xié)程實(shí)現(xiàn)高并發(fā)主要依賴于以下兩點(diǎn):
協(xié)作式多任務(wù)與事件循環(huán)
協(xié)程是協(xié)作式的,這意味著它們會(huì)主動(dòng)在可能發(fā)生阻塞的地方(即 await 處)掛起自己,將控制權(quán)交還給事件循環(huán)。事件循環(huán)則不斷地檢查:有哪些I/O操作已經(jīng)完成了?哪些協(xié)程可以恢復(fù)了?然后調(diào)度執(zhí)行那些就緒的協(xié)程。這個(gè)過程在單線程內(nèi)完成,完全避免了多線程帶來的鎖競(jìng)爭(zhēng)和上下文切換的開銷。
非阻塞I/O與連接池
協(xié)程需要與非阻塞的I/O系統(tǒng)調(diào)用配合才能發(fā)揮最大效能。像 aiohttp 這樣的庫(kù)在底層使用了操作系統(tǒng)的非阻塞I/O機(jī)制(如Linux的 epoll)。當(dāng)一個(gè)協(xié)程發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),這個(gè)請(qǐng)求會(huì)以非阻塞方式立即發(fā)出,然后協(xié)程掛起。事件循環(huán)則通過I/O多路復(fù)用機(jī)制(如 select, poll, epoll)來監(jiān)聽大量socket(網(wǎng)絡(luò)連接)的狀態(tài),一旦某個(gè)socket的數(shù)據(jù)準(zhǔn)備好了,事件循環(huán)就會(huì)喚醒等待該socket的協(xié)程繼續(xù)執(zhí)行。同時(shí),如第一個(gè)搜索結(jié)果強(qiáng)調(diào)的,連接池(Connection Pool) 也至關(guān)重要。通過復(fù)用已經(jīng)建立的TCP連接,可以避免為每個(gè)請(qǐng)求都進(jìn)行耗時(shí)的TCP三次握手,這在高并發(fā)下能帶來數(shù)量級(jí)的性能提升。
協(xié)程、多線程與多進(jìn)程的對(duì)比
理解協(xié)程的適用場(chǎng)景很重要,下面的表格對(duì)比了三種主要的并發(fā)方式:
| 特性 | 協(xié)程 (Coroutine) | 多線程 (Threading) | 多進(jìn)程 (Multiprocessing) |
|---|---|---|---|
| 運(yùn)行模型 | 單線程內(nèi)協(xié)作式切換 | 操作系統(tǒng)線程,搶占式調(diào)度 | 獨(dú)立的操作系統(tǒng)進(jìn)程 |
| 數(shù)據(jù)共享 | 共享內(nèi)存,無需加鎖(但需注意任務(wù)內(nèi)狀態(tài)) | 共享內(nèi)存,需處理線程安全(如鎖) | 內(nèi)存不共享,需通過IPC(如隊(duì)列) |
| 開銷 | 極小,可輕松創(chuàng)建數(shù)萬協(xié)程 | 較大,線程數(shù)受限于內(nèi)存和上下文切換成本 | 巨大,進(jìn)程數(shù)通常與CPU核數(shù)相關(guān) |
| 最佳場(chǎng)景 | I/O密集型任務(wù)(網(wǎng)絡(luò)、文件讀寫) | I/O密集型任務(wù)(但協(xié)程通常更高效) | CPU密集型任務(wù)(計(jì)算、圖像處理) |
| 編程復(fù)雜度 | 中等(需理解 async/await) | 高(需處理復(fù)雜的同步問題) | 中高(需處理進(jìn)程間通信) |
簡(jiǎn)單來說:
I/O密集型(任務(wù)大部分時(shí)間在等待):協(xié)程 >> 多線程 > 多進(jìn)程
CPU密集型(任務(wù)大部分時(shí)間在計(jì)算):多進(jìn)程 > 多線程/協(xié)程(由于Python的GIL,計(jì)算密集型任務(wù)在多線程中無法真正并行)
實(shí)踐建議與常見誤區(qū)
避免在協(xié)程中調(diào)用阻塞性代碼:不要在協(xié)程內(nèi)使用 time.sleep(1) 或同步的 requests.get()。這會(huì)阻塞整個(gè)事件循環(huán),使并發(fā)失效。務(wù)必使用對(duì)應(yīng)的異步版本 await asyncio.sleep(1) 和 aiohttp。
使用信號(hào)量(Semaphore)控制并發(fā)度:雖然協(xié)程很輕量,但向同一個(gè)目標(biāo)服務(wù)器發(fā)起無限度的并發(fā)請(qǐng)求可能會(huì)被視為攻擊或?qū)е聦?duì)方服務(wù)不可用??梢允褂?asyncio.Semaphore 來限制最大并發(fā)數(shù)。
復(fù)用對(duì)象:如網(wǎng)絡(luò)請(qǐng)求的例子所示,在整個(gè)應(yīng)用生命周期內(nèi)復(fù)用 ClientSession 這樣的對(duì)象,以利用連接池。
組合使用多種并發(fā)模型:對(duì)于更復(fù)雜的場(chǎng)景,可以采用“多進(jìn)程+協(xié)程”的混合模式。例如,用多進(jìn)程利用多核CPU,在每個(gè)進(jìn)程內(nèi)部使用協(xié)程處理高并發(fā)的I/O。
到此這篇關(guān)于python協(xié)程實(shí)現(xiàn)高并發(fā)的技術(shù)詳解的文章就介紹到這了,更多相關(guān)python協(xié)程高并發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
windows系統(tǒng)下Boost.Python的配置和使用方法(新手教學(xué))
Boost.Python是Boost庫(kù)的一部分,Boost庫(kù)本身包含了許多其他有用的庫(kù),這篇文章主要介紹了windows系統(tǒng)下Boost.Python的配置和使用方法的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-01-01
Python Numpy運(yùn)行報(bào)錯(cuò):IndexError: too many in
在使用Numpy進(jìn)行數(shù)組操作時(shí),經(jīng)常會(huì)遇到各種錯(cuò)誤,其中,IndexError: too many indices for array是一種常見的錯(cuò)誤,它通常發(fā)生在嘗試使用一個(gè)過多維度的索引來訪問一個(gè)較低維度的數(shù)組時(shí),本文介紹了Python Numpy報(bào)錯(cuò)的解決辦法,需要的朋友可以參考下2024-07-07
Python configparser模塊配置文件解析與應(yīng)用探究
在Python中,configparser模塊是用于處理配置文件的重要工具,本文將全面探討configparser模塊的使用方法,包括讀取、修改、寫入配置文件,以及如何在實(shí)際項(xiàng)目中應(yīng)用該模塊,結(jié)合豐富的示例代碼,將深入剖析該模塊的功能和靈活性2024-01-01
Python Django路徑配置實(shí)現(xiàn)過程解析
這篇文章主要介紹了Python Django路徑配置實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11
Conda創(chuàng)建新環(huán)境的詳細(xì)圖文教程
Anaconda功能龐大,其可以理解為一個(gè)工具,也是一個(gè)可執(zhí)行命令,下面這篇文章主要給大家介紹了關(guān)于Conda創(chuàng)建新環(huán)境的詳細(xì)圖文教程,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01
Python+matplotlib實(shí)現(xiàn)餅圖的繪制
Matplotlib是一個(gè)Python的2D繪圖庫(kù),它以各種硬拷貝格式和跨平臺(tái)的交互式環(huán)境生成出版質(zhì)量級(jí)別的圖形。本文將利用Matplotlib庫(kù)繪制餅圖,感興趣的可以了解一下2022-03-03
Python使用textcase庫(kù)輕松實(shí)現(xiàn)文本格式處理
在Python開發(fā)中,規(guī)范的文本格式處理是提升代碼可讀性和維護(hù)性的關(guān)鍵一環(huán),本文將系統(tǒng)講解textcase庫(kù)的核心功能,典型應(yīng)用場(chǎng)景及性能優(yōu)化策略,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
安裝ElasticSearch搜索工具并配置Python驅(qū)動(dòng)的方法
這篇文章主要介紹了安裝ElasticSearch搜索工具并配置Python驅(qū)動(dòng)的方法,文中還介紹了其與Kibana數(shù)據(jù)顯示客戶端的配合使用,需要的朋友可以參考下2015-12-12

