首頁 > 後端開發 > Python教學 > Python協程的實作方式有哪些

Python協程的實作方式有哪些

WBOY
發布: 2023-05-20 11:08:14
轉載
739 人瀏覽過

什麼是協程

在 Python 中,協程(Coroutine)是一種輕量級的並發程式設計方式,可以透過協作式多任務來實現高效的並發執行。使用 yield 關鍵字掛起函數的執行,以及儲存目前執行狀態,是協程的特殊之處。因此,協程可視為一種特殊的生成器函數。當協程被掛起時,可以使用 send 方法來恢復其執行,並在恢復後傳回一個值。

在 Python 3.4 以前,常使用 yield 關鍵字來實現協程,即稱為「生成器協程」。在 Python 3.4 引入了 asyncio 模組後,可以使用 async/await 關鍵字來定義協程函數,稱為「原生協程」。

協程相比於執行緒和進程,具有以下優點:

  • 輕量級:協程的上下文切換成本很小,可以在單執行緒內並發執行大量的協程。

  • 低延遲:協程的執行過程中,沒有執行緒切換的開銷,也沒有加鎖解鎖的開銷,可以更快地回應外部事件。

  • 高效能:協程的程式碼通常比多執行緒和多進程的程式碼更簡潔可讀,維護成本更低。

協程的使用場景包括網路程式設計、非同步 I/O、資料流處理、高並發任務等。

生成器協程

在 Python 3 中,生成器協程(Generator Coroutine)是指使用生成器函數來實現的協程。生成器函數是一種特殊的函數,其傳回一個生成器對象,可以透過 yield 語句暫停函數的執行,然後在下次呼叫生成器物件的 「next」() 方法時繼續執行。

下面給出一個簡單的生成器協程的範例,其中包含一個生成器函數coroutine 和一個簡單的非同步I/O 操作:

import asyncio

def coroutine():
    print('Coroutine started')
    while True:
        result = yield
        print('Coroutine received:', result)

async def main():
    print('Main started')
    c = coroutine()
    next(c)
    c.send('Hello')
    await asyncio.sleep(1)
    c.send('World')
    print('Main finished')

asyncio.run(main())
登入後複製

結果輸出:

[root@workhost k8s]# python3 test.py 
Main started
Coroutine started
Coroutine received: Hello
Coroutine received: World
Main finished

##4
#來看一下,上面程式碼的執行過程:


main 函數開始執行,列印出Main started。

    建立一個生成器物件 c,呼叫 next(c) 使其執行到第一個 yield 語句處暫停。
  • 使用 c.send('Hello') 恢復生成器函數的執行,並將 'Hello' 作為生成器函數的傳回值。
  • 在等待1秒鐘的過程中,main 函數暫停執行,等待事件循環啟動下一次任務。
  • 在等待1秒鐘後,使用 c.send('World') 繼續執行生成器函數,並將 'World' 作為生成器函數的傳回值。
  • main 函數復原執行,列印出 Main finished。
  • 透過使用生成器函數 coroutine,這段程式碼實現了一個簡單的協程。生成器函數透過使用 yield 語句暫停函數的執行,然後可以透過 send 方法恢復函數的執行,並將值傳遞給生成器函數。透過這種方式,可以使用生成器函數實現非同步並發。使用生成器函數接受非同步 I/O 操作的結果,並將其列印出來,如範例所示。
  • 原生協程

  • Python 3引進原生協程(Native Coroutine)作為一種新協程類型。原生協程是透過使用 async/await 關鍵字來定義的,與生成器協程不同,它們可以像普通函數一樣使用 return 語句傳回值,而不是使用 yield 語句。

下面給出一個簡單的原生協程範例,其中包含一個async 關鍵字修飾的協程函數coroutine 和一個簡單的非同步I/O 操作:

import asyncio

async def coroutine():
    print('Coroutine started')
    await asyncio.sleep(1)
    print('Coroutine finished')

async def main():
    print('Main started')
    await coroutine()
    print('Main finished')

asyncio.run(main())
登入後複製
###結果輸出:# ########[root@workhost k8s]# python3 test.py ###Main started###Coroutine started###Coroutine finished###Main finished#########繼續看一下執行過程:############main 函數開始執行,列印出Main started。 ############呼叫 coroutine 函數,將其作為協程物件運行。 ############在 coroutine 函數中,印出 Coroutine started。 ############在 coroutine 函數中,使用 await asyncio.sleep(1) 暫停函數的執行,等待1秒鐘。 ############在1秒鐘後,恢復 coroutine 函數的執行,並印出 Coroutine finished。 ############main 函數復原執行,列印出 Main finished。 ############在上面的程式碼中,使用async 關鍵字定義了一個原生協程函數coroutine,並在其中使用await 關鍵字來暫停函數的執行,等待非同步I/O 操作的完成。使用原生協程可以編寫並發非同步程式碼,從而提高程式碼的效率和效能。 ###

两种协程对比

Python 3 中,原生协程和生成器协程是不同的协程实现方式,它们分别具有独特的特点和适用场景。下面,通过对比它们的区别和优缺点,才可以更好地理解它们之间的异同,以便选择适合自己的协程实现方式,从而更好地编写高效、可维护的异步程序。

1.区别:

  • 定义方式不同:原生协程使用 async/await 关键字来定义,而生成器协程使用 yield 关键字来定义。

  • 返回方式不同:原生协程使用 return 语句来返回结果,而生成器协程使用 yield 语句来返回结果。

  • 调用方式不同:原生协程使用 await 关键字来调用,而生成器协程使用 yield from 或 yield 语句来调用。

  • 原生协程与生成器协程的实现方式不同,前者使用 asyncio 库,后者则是 Python 语言内置的特性。

2.优缺点:

原生协程的优点:

  • 代码简洁易懂:使用 async/await 关键字,可以编写出更简洁易懂的协程代码。

  • 性能更高:原生协程不需要创建生成器对象,也不需要通过 yield 语句来控制函数的执行流程,因此能够更加高效地处理异步操作。

  • 支持异步 I/O 和任务处理:原生协程可以支持异步 I/O 操作和并发任务处理,可以在处理异步操作时更加灵活。

原生协程的缺点:

  • 兼容性差:原生协程是 Python 3.5 版本之后才引入的新特性,因此在旧版本的 Python 中无法使用。

  • 异常处理不方便:原生协程在处理异常时比较麻烦,需要使用 try/except 语句来处理。

生成器协程的优点:

  • 兼容性好:生成器协程是 Python 2 和 Python 3 都支持的特性。

  • 可读性好:生成器协程使用 yield 关键字来实现,代码逻辑清晰易懂。

  • 异常处理方便:生成器协程在处理异常时比较方便,可以使用 try/except 语句来处理。

生成器协程的缺点:

  • 性能相对较低:生成器协程需要创建生成器对象,也需要通过 yield 语句来控制函数的执行流程,因此处理异步操作时性能相对较低。

  • 功能有限:生成器协程不能像原生协程一样支持异步 I/O 操作和任务处理。

实战案例

接下来,模拟一个场景,假设实现一个异步的批量处理任务的工具,使用原生协程来实现。

看下面代码:

import asyncio
import random

async def batch_process_task(tasks, batch_size=10):
    # 将任务列表划分为多个批次
    for i in range(0, len(tasks), batch_size):
        batch = tasks[i:i+batch_size]
        # 使用原生协程来异步处理每个批次的任务
        await asyncio.gather(*[process_task(task) for task in batch])

async def process_task(task):
    # 模拟任务处理过程
    await asyncio.sleep(random.uniform(0.5, 2.0))
    print("Task {} processed".format(task))

async def main():
    # 构造任务列表
    tasks = [i for i in range(1, 101)]
    # 并发处理批量任务
    await batch_process_task(tasks, batch_size=10)

if __name__ == '__main__':
    asyncio.run(main())
登入後複製

输出:

[root@workhost k8s]# python3 test.py 
Task 9 processed
Task 10 processed
Task 1 processed
Task 8 processed
Task 6 processed
Task 4 processed
Task 3 processed
Task 2 processed
Task 5 processed
...
...

batch_process_task函数使用原生协程来处理每个批次的任务,而process_task函数则是处理每个任务的函数。在main函数中,任务列表会被构造,并使用batch_process_task函数来异步地处理批量任务。

以上是Python協程的實作方式有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板