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


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

並發、並行、同步和異步

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

但是因為程序切換的速度非常快,1 秒鍾內可以完全很多次程序切換,肉眼無法感知。
bingfa.jpg

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

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

協程,線程和進程的區別

多進程通常利用的是多核 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 第三方庫用法

參考文獻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM