iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0

第二十一天:Python 中的異步程式設計(Asynchronous Programming)

內容概述:

今天,我們要探討的是 Python 異步程式設計(Asynchronous Programming)。隨著現代應用的複雜度增加,特別是在處理大量 I/O 操作(如網路請求或文件讀寫)時,異步程式設計能夠提升效能,讓你的程式不必因為等待而停滯。這節課將讓 0 基礎的學生理解異步程式設計的概念、用法,以及如何在日常程式中應用。

課程大綱:

  1. 同步 vs. 異步

    • 同步程式是線性的,按順序執行每一行程式碼。異步程式則允許程式在等待 I/O 操作時繼續處理其他任務。
    • 當一個同步任務在等待,程式會被 "阻塞"。相反,異步可以避免阻塞,讓程式高效執行多個任務。
  2. 基本異步語法:asyncawait

    • Python 提供了異步的關鍵字:asyncawait。這兩個關鍵字幫助你定義異步函式並等待其結果。
    import asyncio
    
    async def say_hello():
        print("Hello!")
        await asyncio.sleep(1)
        print("Hello again after 1 second!")
    
    asyncio.run(say_hello())
    

    在這個例子中,我們使用了 async 來定義異步函式 say_hello,並使用 await 等待一個模擬的 I/O 操作 asyncio.sleep(1)(等待 1 秒)。

  3. 異步任務的運行

    • 我們可以通過 asyncio.run() 來啟動異步任務。在 Python 3.7 之後,這是最簡單的方法來執行單個異步函式。
  4. 多個任務的並行運行

    • 異步程式設計的強大之處在於同時運行多個任務而不必等待前一個任務完成。這可以通過 asyncio.gather() 來實現:
    import asyncio
    
    async def task1():
        print("Task 1 started")
        await asyncio.sleep(2)
        print("Task 1 finished")
    
    async def task2():
        print("Task 2 started")
        await asyncio.sleep(1)
        print("Task 2 finished")
    
    async def main():
        await asyncio.gather(task1(), task2())
    
    asyncio.run(main())
    

    在這裡,task1()task2() 將並行執行。雖然 task1 要等待 2 秒,但 task2 會在這期間完成。

  5. 異步 I/O 操作應用

    • 異步程式設計非常適合處理大量 I/O 操作,特別是網路請求。我們可以使用 aiohttp 這樣的庫來異步執行網路操作。
    import aiohttp
    import asyncio
    
    async def fetch_data(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                return await response.text()
    
    async def main():
        url = "https://www.example.com"
        content = await fetch_data(url)
        print(content)
    
    asyncio.run(main())
    

    這裡我們使用了 aiohttp 來異步進行 HTTP 請求,並在獲取數據後立即處理。

  6. 異步程式設計的優勢與挑戰

    • 優勢: 異步能夠大幅提升 I/O 密集型應用的效能,例如網站後端、檔案處理和網路爬蟲。
    • 挑戰: 異步程式可能會使程式碼更加複雜,特別是在多個任務之間進行協調和錯誤處理時。理解 awaitasync 的運作原理對初學者來說可能需要時間適應。

實作練習:

  1. 任務一:異步任務並行運行

    • 請建立兩個異步函式 task1task2,讓它們各自等待不同的秒數,並使用 asyncio.gather() 來同時執行它們。觀察兩個任務的運行順序。
  2. 任務二:異步網路請求

    • 使用 aiohttp 庫,從至少兩個不同的網站發起 HTTP 請求,並同時獲取和顯示它們的數據。觀察它們的回應時間,並思考如果使用同步方法,這會花費多少時間。

這些練習將幫助你熟悉 Python 的異步編程,使用 asyncioaiohttp 來進行任務並行執行和異步網路請求。以下是每個任務的詳細解釋和範例代碼。


任務一:異步任務並行運行
目標:
  1. 建立兩個異步函式 task1task2,各自等待不同的秒數。
  2. 使用 asyncio.gather() 同時執行兩個任務,並觀察運行的順序。
步驟:
  1. 定義兩個異步函式,模擬不同的等待時間。
  2. 使用 asyncio.gather() 來並行運行它們。
  3. 打印結果以觀察任務的執行順序。
範例代碼:
import asyncio

# 定義異步函式 task1
async def task1():
    print("Task 1 開始")
    await asyncio.sleep(2)  # 模擬等待 2 秒
    print("Task 1 結束")
    return "Result of Task 1"

# 定義異步函式 task2
async def task2():
    print("Task 2 開始")
    await asyncio.sleep(1)  # 模擬等待 1 秒
    print("Task 2 結束")
    return "Result of Task 2"

# 定義主要的異步函式
async def main():
    # 使用 asyncio.gather() 同時執行 task1 和 task2
    results = await asyncio.gather(task1(), task2())
    print(results)

# 運行異步函式
asyncio.run(main())
解釋:
  • async def 定義了異步函式,這些函式允許使用 await 關鍵字來等待某些操作的完成。
  • asyncio.gather() 可以將多個異步任務一起執行,使它們並行運行。
  • asyncio.run() 用來運行異步主函式 main()
  • task2 會先完成,因為它的等待時間比較短,這可以顯示異步任務如何提高效率。

任務二:異步網路請求
目標:
  1. 使用 aiohttp 庫發起兩個不同網站的 HTTP 請求。
  2. 同時獲取和顯示它們的數據,並觀察它們的回應時間。
  3. 思考如果使用同步方法,這些請求會花費多少時間。
步驟:
  1. 安裝 aiohttp(如果還沒有):
    pip install aiohttp
    
  2. 定義兩個異步函式來發起 HTTP 請求,並使用 asyncio.gather() 同時執行它們。
  3. 打印回應時間和數據。
範例代碼:
import aiohttp
import asyncio
import time

# 定義異步函式來發送 HTTP 請求
async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            print(f"從 {url} 獲取數據中...")
            return await response.text()

# 定義主要的異步函式
async def main():
    urls = [
        "https://www.example.com",
        "https://www.httpbin.org/get"
    ]
    
    start_time = time.time()

    # 使用 asyncio.gather 同時發送請求
    responses = await asyncio.gather(fetch(urls[0]), fetch(urls[1]))
    
    for i, response in enumerate(responses):
        print(f"網站 {urls[i]} 的回應長度為: {len(response)}")

    # 計算總時間
    print(f"異步請求總耗時: {time.time() - start_time:.2f} 秒")

# 運行異步函式
asyncio.run(main())
解釋:
  • aiohttp.ClientSession() 用來創建一個會話,用於發送和接收 HTTP 請求。
  • async with session.get(url) 發起異步請求,並使用 await response.text() 來獲取完整的回應內容。
  • asyncio.gather() 允許我們同時向多個網站發送請求,而不是依次等待每個請求完成。
  • 當執行兩個異步請求時,總的運行時間應該會接近最長的那一個請求,而非兩個請求時間的總和。相比同步方法,它能顯著減少總耗時。

思考:
  • 同步與異步的區別:如果我們使用同步方式發起 HTTP 請求,則必須等待每個請求完全完成,這會顯著增加總耗時。在異步編程中,所有請求同時進行,可以節省時間,提高性能。

這兩個任務展示了如何使用異步編程來有效處理並行任務和網絡請求,這在現代應用程序中非常有用。


心得與小結:

異步程式設計能夠顯著提升應用程式的效能,特別是在處理大量 I/O 操作時。透過 asyncawait 關鍵字,初學者能夠輕鬆上手異步程式設計。雖然異步的概念對 0 基礎的學生可能會稍顯複雜,但隨著實踐和理解,異步將成為日常開發中的得力工具。建議在日常程式開發中漸進地使用異步模式,從小型應用開始實踐。


希望今天的課程能讓你更清楚異步程式設計的概念並實際應用在你的 Python 程式中。


上一篇
跟著 ChatGPT成為程式大佬!Python 中的生成器&迭代器
下一篇
跟著 ChatGPT成為程式大佬!Python 中的模塊與套件
系列文
如果讓chatgpt參加iThome鐵人賽,他竟然寫出...!?31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言