python教程:使用 async 和 await 協程進行併發編程

python 一直在進行併發編程的優化, 比較熟知的是使用 thread 模塊多線程和 multiprocessing 多進程,後來慢慢引入基於 yield 關鍵字的協程。 而近幾個版本,python 對於協程的寫法進行了大幅的優化,不少以前的協程寫法不被官方推薦了。若是你以前瞭解過 python 協程,你應該看看最新的用法。html

併發、並行、同步和異步

併發指的是 一個 CPU 同時處理多個程序,可是在同一時間點只會處理其中一個。併發的核心是:程序切換。python

可是由於程序切換的速度很是快,1 秒鐘內能夠徹底不少次程序切換,肉眼沒法感知。
bingfa.jpg
git

並行指的是多個 CPU 同時處理多個程序,同一時間點能夠處理多個。
並行.jpg
github

同步:執行 IO 操做時,必須等待執行完成才獲得返回結果。
異步:執行 IO 操做時,沒必要等待執行就能獲得返回結果。
yibu.jpg

web

協程,線程和進程的區別

多進程一般利用的是多核 CPU 的優點,同時執行多個計算任務。每一個進程有本身獨立的內存管理,因此不一樣進程之間要進行數據通訊比較麻煩。數據庫

多線程是在一個 cpu 上建立多個子任務,當某一個子任務休息的時候其餘任務接着執行。多線程的控制是由 python 本身控制的。 子線程之間的內存是共享的,並不須要額外的數據通訊機制。可是線程存在數據同步問題,因此要有鎖機制。編程

協程的實現是在一個線程內實現的,至關於流水線做業。因爲線程切換的消耗比較大,因此對於併發編程,能夠優先使用協程。api

。。。
這是對比圖:
網絡

協程的基礎使用

這是 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 第三方庫用法

參考文獻

相關文章
相關標籤/搜索