python 一直在進行併發程式設計的優化, 比較熟知的是使用 thread 模組多執行緒和 multiprocessing 多程式,後來慢慢引入基於 yield 關鍵字的協程。 而近幾個版本,python 對於協程的寫法進行了大幅的優化,很多之前的協程寫法不被官方推薦了。如果你之前瞭解過 python 協程,你應該看看最新的用法。
併發、並行、同步和非同步
併發指的是 一個 CPU 同時處理多個程式,但是在同一時間點只會處理其中一個。併發的核心是:程式切換。
但是因為程式切換的速度非常快,1 秒鐘內可以完全很多次程式切換,肉眼無法感知。
並行指的是多個 CPU 同時處理多個程式,同一時間點可以處理多個。
同步:執行 IO 操作時,必須等待執行完成才得到返回結果。
非同步:執行 IO 操作時,不必等待執行就能得到返回結果。
協程,執行緒和程式的區別
多程式通常利用的是多核 CPU 的優勢,同時執行多個計算任務。每個程式有自己獨立的記憶體管理,所以不同程式之間要進行資料通訊比較麻煩。
多執行緒是在一個 cpu 上建立多個子任務,當某一個子任務休息的時候其他任務接著執行。多執行緒的控制是由 python 自己控制的。 子執行緒之間的記憶體是共享的,並不需要額外的資料通訊機制。但是執行緒存在資料同步問題,所以要有鎖機制。
協程的實現是在一個執行緒內實現的,相當於流水線作業。由於執行緒切換的消耗比較大,所以對於併發程式設計,可以優先使用協程。
。。。
這是對比圖:
協程的基礎使用
這是 python 3.7 裡面的基礎協程用法,現在這種用法已經基本穩定,不太建議使用之前的語法了。
import asyncio
import time
async def visit_url(url, response_time):
"""訪問 url"""
await asyncio.sleep(response_time)
return f"訪問{url}, 已得到返回結果"
start_time = time.perf_counter()
task = visit_url('http://wangzhen.com', 2)
asyncio.run(task)
print(f"消耗時間:{time.perf_counter() - start_time}")
- 1, 在普通的函式前面加 async 關鍵字;
- 2,await 表示在這個地方等待子函式執行完成,再往下執行。(在併發操作中,把程式控制權教給主程式,讓他分配其他協程執行。) await 只能在帶有 async 關鍵字的函式中執行。
- 3, asynico.run() 執行程式
- 4, 這個程式消耗時間 2s 左右。
增加協程
再新增一個任務:
task2 = visit_url('http://another.com', 3)
asynicio.run(task2)
這 2 個程式一共消耗 5s 左右的時間。並沒有發揮併發程式設計的優勢。如果是併發程式設計,這個程式只需要消耗 3s,也就是task2的等待時間。要想使用併發程式設計形式,需要把上面的程式碼改一下。
import asyncio
import time
async def visit_url(url, response_time):
"""訪問 url"""
await asyncio.sleep(response_time)
return f"訪問{url}, 已得到返回結果"
async def run_task():
"""收集子任務"""
task = visit_url('http://wangzhen.com', 2)
task_2 = visit_url('http://another', 3)
await asyncio.run(task)
await asyncio.run(task_2)
asyncio.run(run_task())
print(f"消耗時間:{time.perf_counter() - start_time}")
asyncio.gather 會建立 2 個子任務,當出現 await 的時候,程式會在這 2 個子任務之間進行排程。
create_task
建立子任務除了可以用 gather 方法之外,還可以使用 asyncio.create_task 進行建立。
async def run_task():
coro = visit_url('http://wangzhen.com', 2)
coro_2 = visit_url('http://another.com', 3)
task1 = asyncio.create_task(coro)
task2 = asyncio.create_task(coro_2)
await task1
await task2
協程的主要使用場景
協程的主要應用場景是 IO 密集型任務,總結幾個常見的使用場景:
- 網路請求,比如爬蟲,大量使用 aiohttp
- 檔案讀取, aiofile
- web 框架, aiohttp, fastapi
- 資料庫查詢, asyncpg, databases
進一步學習方向(接下來的文章)
- 什麼時候用協程,什麼時候用多執行緒,什麼時候用多程式
- future 物件
- asyncio 的底層 api
- loop
- trio 第三方庫用法