1. 程式人生 > 實用技巧 >Python協程

Python協程

目錄

由於Golang的goroutine太過簡單易用,我再次嘗試了一遍Python的asyncio。

1. 簡介

協程,英文叫作 Coroutine,又稱微執行緒、纖程,協程是一種使用者態的輕量級執行緒。

協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧。因此協程能保留上一次呼叫時的狀態,即所有區域性狀態的一個特定組合,每次過程重入時,就相當於進入上一次呼叫的狀態。

協程本質上是個單程序,協程相對於多程序來說,無需執行緒上下文切換的開銷,無需原子操作鎖定及同步的開銷,程式設計模型也非常簡單。

我們可以使用協程來實現非同步操作,比如在網路爬蟲場景下,我們發出一個請求之後,需要等待一定的時間才能得到響應,但其實在這個等待過程中,程式可以幹許多其他的事情,等到響應得到之後才切換回來繼續處理,這樣可以充分利用 CPU 和其他資源,這就是協程的優勢。

1.1. 協程相對於多執行緒的優點

多執行緒程式設計是比較困難的,因為排程程式任何時候都能中斷執行緒, 必須記住保留鎖,去保護程式中重要部分, 防止多執行緒在執行的過程中斷。

而協程預設會做好全方位保護, 以防止中斷。我們必須顯示產出才能讓程式的餘下部分執行。對協程來說, 無需保留鎖, 而在多個執行緒之間同步操作, 協程自身就會同步, 因為在任意時刻, 只有一個協程執行。總結下大概下面幾點:

  • 無需系統核心的上下文切換,減小開銷;
  • 無需原子操作鎖定及同步的開銷,不用擔心資源共享的問題;
  • 單執行緒即可實現高併發,單核 CPU 即便支援上萬的協程都不是問題,所以很適合用於高併發處理,尤其是在應用在網路爬蟲中。

2. 操作

homepage

Python協程還不理解?請收下這份超詳細的非同步程式設計教程

相比於Golang中的goroutine,Python的協程還需要理解下面幾個概念。

  • event_loop:事件迴圈,相當於一個無限迴圈,我們可以把一些函式註冊到這個事件迴圈上,當滿足條件發生的時候,就會呼叫對應的處理方法。
  • coroutine:中文翻譯叫協程,在 Python 中常指代為協程物件型別,我們可以將協程物件註冊到事件迴圈中,它會被事件迴圈呼叫。我們可以使用 async
    關鍵字來定義一個方法,這個方法在呼叫時不會立即被執行,而是返回一個協程物件。
  • task:任務,它是對協程物件的進一步封裝,包含了任務的各個狀態。
  • future:代表將來執行或沒有執行的任務的結果,實際上和 task 沒有本質區別。

另外我們還需要了解 async/await 關鍵字,它是從 Python 3.5 才出現的,專門用於定義協程。其中,async 定義一個協程,await ## 用來掛起阻塞方法的執行。

2.1. 定義非同步函式

  • 使用 async def function(params)
  • 使用 await 定義IO操作(Python通過該關鍵字在耗時的IO運算上切換協程,並自動監聽)
async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print(f"started at {time.strftime('%X')}")

    await say_after(1, 'hello')
    await say_after(2, 'world')

    print(f"finished at {time.strftime('%X')}")

2.2. 任務 & 事件迴圈

homepage

  • 建立任務
    • asyncio.create_task() # Python 3.7
    • asyncio.ensure_future() # Python 3.7 之前
  • 建立事件迴圈
    • loop = asyncio.get_event_loop()
    • task = loop.create_task(coroutine)
    • loop.run_until_complete(future)
    • loop.run_forever() 執行事件迴圈直到 stop() 被呼叫。
    • loop.stop()
async def coro():
    ...

async def main():
    # In Python 3.7+
    task = asyncio.create_task(coro())
    ...

    # This works in all Python versions but is less readable
    task = asyncio.ensure_future(coro())

    await task  # 簡單等待(掛起當前協程並切換IO)直到完成,程式繼續
    ...

asyncio.run(main())

2.3. Task回撥

add_done_callback(callback, *, context=None)

新增一個回撥,將在 Task 物件完成時被執行。

2.4. 執行 asyncio 程式

async def main():
    await asyncio.sleep(1)
    print('hello')

asyncio.run(main())

2.5. 簡單等待

coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

併發執行 aws 指定的 可等待物件 並阻塞執行緒直到滿足 return_when 指定的條件。返回 Task/Future 集合: (done, pending)

注意:【Python3.8】直接向 wait() 傳入協程物件的方式已棄用。

最新推薦用法:

async def foo():
    return 42

task = asyncio.create_task(foo())
done, pending = await asyncio.wait({task})

if task in done:
    # Everything will work as expected now.