什么是协程

在 Python 中,协程(Coroutine)是一种轻量级的并发编程形式,能够通过合作式多任务来实现高效的并发执行。协程是一种非凡的生成器函数,通过应用 yield 关键字来挂起函数的执行,并保留以后的执行状态。协程的执行能够通过 send 办法来复原,并在下一次挂起时返回一个值。

在 Python 3.4 之前,协程通常应用 yield 关键字来实现,称为“生成器协程”。在 Python 3.4 引入了 asyncio 模块后,能够应用 async/await 关键字来定义协程函数,称为“原生协程”。

协程相比于线程和过程,具备以下长处:

  1. 轻量级:协程的上下文切换老本很小,能够在单线程内并发执行大量的协程。
  2. 低提早:协程的执行过程中,没有线程切换的开销,也没有加锁解锁的开销,能够更快地响应内部事件。
  3. 高效性:协程的代码通常比多线程和多过程的代码更加简洁和可读,保护老本更低。
协程的应用场景包含网络编程、异步 I/O、数据流解决、高并发工作等。

生成器协程

在 Python 3 中,生成器协程(Generator Coroutine)是指应用生成器函数来实现的协程。生成器函数是一种非凡的函数,其返回一个生成器对象,能够通过 yield 语句暂停函数的执行,而后在下一次调用生成器对象的 「next」() 办法时继续执行。

上面给出一个简略的生成器协程的示例,其中蕴含一个生成器函数 coroutine 和一个简略的异步 I/O 操作:

import asynciodef 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 startedCoroutine startedCoroutine received: HelloCoroutine received: WorldMain finished

来看一下,下面代码的执行过程:

  1. main 函数开始执行,打印出 Main started。
  2. 创立一个生成器对象 c,调用 next(c) 使其执行到第一个 yield 语句处暂停。
  3. 应用 c.send('Hello') 复原生成器函数的执行,并将 'Hello' 作为生成器函数的返回值。
  4. 在期待1秒钟的过程中,main 函数暂停执行,期待事件循环发动下一次工作。
  5. 在期待1秒钟后,应用 c.send('World') 继续执行生成器函数,并将 'World' 作为生成器函数的返回值。
  6. main 函数复原执行,打印出 Main finished。

在下面的代码中,应用生成器函数 coroutine 实现了一个简略的协程。生成器函数通过应用 yield 语句暂停函数的执行,而后能够通过 send 办法复原函数的执行,并将值传递给生成器函数。通过这种形式,能够应用生成器函数实现异步并发。在下面的示例中,应用生成器函数接管并打印异步 I/O 操作的后果。

原生协程

Python 3 引入了原生协程(Native Coroutine)作为一种新的协程类型。原生协程是通过应用 async/await 关键字来定义的,与生成器协程不同,它们能够像一般函数一样应用 return 语句返回值,而不是应用 yield 语句。

上面给出一个简略的原生协程示例,其中蕴含一个 async 关键字润饰的协程函数 coroutine 和一个简略的异步 I/O 操作:

import asyncioasync 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 startedCoroutine startedCoroutine finishedMain finished

持续看一下执行过程:

  1. main 函数开始执行,打印出 Main started。
  2. 调用 coroutine 函数,将其作为一个协程对象运行。
  3. 在 coroutine 函数中,打印出 Coroutine started。
  4. 在 coroutine 函数中,应用 await asyncio.sleep(1) 暂停函数的执行,期待1秒钟。
  5. 在1秒钟后,复原 coroutine 函数的执行,并打印出 Coroutine finished。
  6. main 函数复原执行,打印出 Main finished。

在下面的代码中,应用 async 关键字定义了一个原生协程函数 coroutine,并在其中应用 await 关键字来暂停函数的执行,期待异步 I/O 操作的实现。通过这种形式,能够在原生协程中编写异步并发代码,从而进步代码的性能和效率。

两种协程比照

Python 3 中原生协程和生成器协程是两种不同的协程实现形式,它们各自有本人的特点和实用场景。上面,通过比照它们的区别和优缺点,才能够更好地了解它们之间的异同,以便抉择适宜本人的协程实现形式,从而更好地编写高效、可保护的异步程序。

  1. 区别:
  • 定义形式不同:原生协程应用 async/await 关键字来定义,而生成器协程应用 yield 关键字来定义。
  • 返回形式不同:原生协程应用 return 语句来返回后果,而生成器协程应用 yield 语句来返回后果。
  • 调用形式不同:原生协程应用 await 关键字来调用,而生成器协程应用 yield from 或 yield 语句来调用。
  • 外部实现不同:原生协程通过 asyncio 库来实现,而生成器协程是 Python 语言内置的个性。
  1. 优缺点:
  • 原生协程的长处:
    • 代码简洁易懂:应用 async/await 关键字,能够编写出更简洁易懂的协程代码。
    • 性能更高:原生协程不须要创立生成器对象,也不须要通过 yield 语句来管制函数的执行流程,因而可能更加高效地解决异步操作。
    • 反对异步 I/O 和工作解决:原生协程能够反对异步 I/O 操作和并发工作解决,能够在解决异步操作时更加灵便。
  • 原生协程的毛病:
    • 兼容性差:原生协程是 Python 3.5 版本之后才引入的新个性,因而在旧版本的 Python 中无奈应用。
    • 异样解决不不便:原生协程在解决异样时比拟麻烦,须要应用 try/except 语句来解决。
  • 生成器协程的长处:
    • 兼容性好:生成器协程是 Python 2 和 Python 3 都反对的个性。
    • 可读性好:生成器协程应用 yield 关键字来实现,代码逻辑清晰易懂。
    • 异样解决不便:生成器协程在解决异样时比拟不便,能够应用 try/except 语句来解决。
  • 生成器协程的毛病:
    • 性能绝对较低:生成器协程须要创立生成器对象,也须要通过 yield 语句来管制函数的执行流程,因而解决异步操作时性能绝对较低。
    • 性能无限:生成器协程不能像原生协程一样反对异步 I/O 操作和工作解决。

实战案例

接下来,模仿一个场景,假如实现一个异步的批量解决工作的工具,应用原生协程来实现。

看上面代码:

import asyncioimport randomasync 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 processedTask 10 processedTask 1 processedTask 8 processedTask 6 processedTask 4 processedTask 3 processedTask 2 processedTask 5 processed......

batch_process_task函数应用原生协程来解决每个批次的工作,而process_task函数则是解决每个工作的函数。main函数则是结构工作列表,并且应用batch_process_task函数来异步地解决批量工作。

本文转载于WX公众号:不背锅运维(喜爱的盆友关注咱们):https://mp.weixin.qq.com/s/GkhvW9qTCjw89xy0gLgPGQ