首頁 > 軟體

Python使用Asyncio進行web程式設計方法詳解

2022-08-05 22:01:15

前言

許多 Web 應用依賴大量的 I/O (輸入/輸出) 操作,比如從網站上下載圖片、視訊等內容;進行網路聊天或者針對後臺資料庫進行多次查詢。資料庫查詢可能會耗費大量時間,尤其是在該資料庫處於高負載或查詢很複雜的情況下。 Web 伺服器可能需要同時處理數百或數千個請求。

I/O 是指計算機的輸入和輸出裝置,例如鍵盤、硬碟機,以及最常見的網路卡。這些操作等待使用者輸入或從基於 Web 的 API 檢索內容。

Asynchronous IO (async IO) 是一種非同步程式設計設計,並在 Python 3.4 的 asyncio 模組中得到了支援,作為在多執行緒和多程序之外處理這些高並行工作負載的另一種方法,可以顯著提高使用 I/O 操作的應用程式的效能和資源利用率。

什麼是同步程式設計

同步程式設計,通常來說,大多數程式語言都是子例程呼叫模型:按照順序執行程式碼。在此模型中,下一行程式碼在前一行程式碼完成後立即執行,並且一次只完成一個模組。

該模型適用於大部分應用程式。但是,也存在明顯的缺點,如果一行程式碼特別慢怎麼辦?

在這種情況下,速度慢的程式碼將導致所有其他程式碼都將被卡住,直到該行完成。最差的情況下可能導致整個應用程式卡死。可能大多數人在某些軟體操作中,一個小小的操作導致整個系統執行不下去,最後只能重啟。

什麼是非同步程式設計

為了解決同步模型的問題,引入了非同步程式設計的概念,意味著允許同一時刻執行多個任務。

非同步程式設計模型意味著需要長時間執行的任務可以在後臺執行,與主應用程式分開。系統可以自由地執行不依賴於該任務的其他工作,而不是阻止所有其他應用程式程式碼等待該長時間執行的任務完成。然後,一旦長時間執行的任務完成,我們會收到通知它已完成。

asyncio 庫允許我們使用非同步程式設計模型執行程式碼。 這讓我們可以一次處理多個 I/O 操作,同時仍然允許我們的應用程式保持響應。

在 Python 3.4 中,asyncio 庫中包含了裝飾器和生成器 yield from 來定義協程(coroutine)。協程是一種方法,當我們有一個可能長時間執行的任務時可以暫停,然後在該任務完成時恢復。

協程執行完成後返回到呼叫者有一種新方法:通過 yield 控制。當協程的 yield 執行完成後立即回到了呼叫點,但是對協程的再次呼叫不會在起始處再次開始,相反,他們繼續從最近停止處繼續進行。

如下圖所示:

def filter_even(numbers):
    for num in range(numbers):
        if (num % 2 == 0):
            yield num
even_number = filter_even(100)
print(list(even_number))

執行結果:

$ python yielddemo.py 
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98]

ayncio 版 Hello 程式

Python 3.5 版中,當關鍵字 async 和 await 顯式新增到語言中時,該語言實現了對協程和非同步程式設計的一流支援。 這種語法在 C# 和 JavaScript 等其他程式語言中很常見,它允許我們使非同步程式碼看起來像是同步執行的。 這使得非同步程式碼易於閱讀和理解,因為它看起來像大多數軟體工程師熟悉的順序流程。 asyncio 是一個使用稱為單執行緒事件迴圈的並行模型以非同步方式執行這些協程的庫。

利用 async/await 兩個定義關鍵字定義協程,通過 asyncio 提供執行和管理協程的基礎:

import asyncio
import time
async def main():
    print(f'{time.ctime()} Hello!')
    await asyncio.sleep(1.0)
    print(f'{time.ctime()} See you again!')
asyncio.run(main())

執行結果:

$ python asynciodemo.py 
Sat Jul  9 23:19:40 2022 Hello!
Sat Jul  9 23:19:41 2022 See you again!

asyncio 提供了一個 run() 函數來執行 async def 函數,然後從那裡呼叫的所有其他協程,如 main() 函數中的 sleep() 函數。asyncio 不是多執行緒或多程序,而是並行執行程式碼。

JavaScript 中支援非同步執行(瀏覽器,Nodejs,Electron 等)。在早期版本中,他們只是使用回撥功能在非同步操作完成後執行其他功能。

如何使用 asyncio

建立協程很簡單,與建立普通的 Python 函數沒有太大區別。唯一的區別是,我們不是使用 def 關鍵字,而是 使用 async def 。async 關鍵字將函數標記為協程,而不是普通的 Python 函數。

import asyncio
import time
def write(msg):
    print(msg, flush=True)
async def say1():
    await asyncio.sleep(1)
    write("Hello from 1")
async def say2():
    await asyncio.sleep(1)
    write("Hello from 2")
write("start")
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
    say1(),
    say2()
))
write("exit")
loop.close()

執行該程式碼,可以看到 Hello from 1 執行 1 秒後執行 Hello from 2

$ python asyncoidemo2.py 
start
Hello from 1
Hello from 2
exit

run_until_complete 執行 say() 函數,直譯器會逐行執行該函數的內容。當碰到 await 之後,直譯器開始非同步操作:這個操作為了迴圈將完成一些內部回撥操作,這個回撥操作是對開發人員隱藏的。但是現在,say1 開始後,它立即將控制返回到事件迴圈。所以,它啟動非同步 sleep 和控制迴圈,然後迴圈實際上已經開始啟動 say2 函數。

當第一次非同步 sleep 執行 1秒後,進入內部回撥執行 say1 協程,下一個操作是列印 Hello from 1。列印後,它再次返回到活動迴圈。同時,從第二次睡眠開始,迴圈獲得了有關完成第二次睡眠的事件。

所以接下來 Hello from 2 列印,然後第二種方法也返回。

run_until_complete(gather(l1,l2,l3)) 將阻止所有 l1,l2,l3 Coroutines:

請注意,7 和 9 事件可能會交換 - 如果您多次執行程式碼,您可能會注意到 Hello from 1 列印在 Hello from 2 之後。

  • event_loop 事件迴圈:程式開啟一個無限迴圈,把一些函數註冊到事件迴圈上,當滿足事件發生的時候,呼叫相應的協程函數
  • coroutine 協程:協程物件,指一個使用 async 關鍵字定義的函數,它的呼叫不會立即執行函數,而是會返回一個協程物件。協程物件需要註冊到事件迴圈,由事件迴圈呼叫。
  • task 任務:一個協程物件就是一個原生可以掛起的函數,任務則是對協程進一步封裝,其中包含了任務的各種狀態
  • future: 代表將來執行或沒有執行的任務的結果。它和 task 上沒有本質上的區別
  • async/await 關鍵字:python3.5 用於定義協程的關鍵字,async 定義一個協程,await 用於掛起阻塞的非同步呼叫介面。

總結

本文首先介紹了同步程式設計和非同步程式設計的概念,然後引出了協程的基本概念,寫了 asyncio 版的 HelloWorld 程式,最後給出了 Python 中 asyncio 庫的簡易使用方法。

協程的優勢在於多 IO 操作時能夠有效提高程式速度,例如某些 HTTP 使用者端,例如 aiohttps 呼叫伺服器中就利用上了 asyncio 庫。

參考連結:Async IO in Python: A Complete Walkthrough

以上就是Python使用Asyncio進行web程式設計方法詳解的詳細內容,更多關於Python Asyncio web程式設計的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com