协程

 

在 Python 中,协程通常使用 asyncawait 关键字来定义和管理。通过使用这些关键字,你可以定义异步函数(async function)和异步上下文管理器(async context manager),这些函数和上下文管理器可以在需要时挂起(暂停)和恢复执行。这种方式可以让程序员编写类似于同步代码的异步程序,从而更容易地处理并发任务。但协程对于内核来说依旧是一个线程,并不是真正的并行运行。

协程的优点包括:

  1. 轻量级: 与线程相比,协程的创建和切换开销更小,因为它们在单个线程中运行。
  2. 简单: 使用 asyncawait 关键字,可以更容易地编写并发代码,避免了传统多线程编程中的锁和同步问题。
  3. 高效: 在 I/O 密集型任务中,协程可以更好地利用系统资源,因为它们可以在等待 I/O 操作完成时挂起,而不会阻塞整个线程。

协程跟线程的区别:
线程是通过在内核态进行系统调度、切换、销毁的,而协程是在用户态,通过程序本身来进行调度
和切换,由于协程会将寄存器上下文和栈保存到其他地方,在切换回来时,会恢复之前保存的寄存器上
下文和栈。
因此协程在没有系统的参与下,整体性能会非常好,没有了线程之间切换的开销,可以获得很高的
运行效率; 同时由于不用线程切换,也就意味着协程不会出现同时访问同一个数据,造成线程安全的问
题,因此协程是安全的,不需要多线程的锁机制,所以基本上能用多线程的地方,也可以用协程,

但在 Linux 内核中,单个线程通常只能在单个核心上执行。这是因为在默认情况下,Linux 内核使用的调度器(scheduler)通常会将一个线程限制在单个 CPU 核心上执行。这种限制有助于避免竞争条件和数据一致性问题。

如果想要利用多核性能,则需要做一定优化,比如使用多线程技术或者利用一些处理器提供的向量化指令集(如 Intel 的 SSE、AVX 指令集)进行优化,就像Archlinux的ALHP 软件源将 Arch Linux 官方源中的软件包进行了重新编译,使用了 x86-64-v2x86-64-v3 优化,这些优化就包含了向量化指令集的优化,可以提升一部分并行性能。

使用方法

使用异步/协程 需要引入asyncio

关键字

  • async 放在函数前的声明,声明他是一个协程(异步)函数。
  • await  用于表示此段逻辑需要进行挂起,await后面跟的一定是一个协程对象(可等待对象)。

可等待对象包括协程(async def定义的协程函数和其返回的对象)、任务(task)以及feature。

Future 是一种特殊的 低层级 可等待对象,表示一个异步操作的 最终结果。是async/await的底层对象,一般不建议在应用层直接使用。

定义一个协程函数

import asyncio
async def func():
	print('Hello!')
	await dosomething() # 或者 await asyncio.sleep(1)
	print('world')

 

启动协程

注意:简单地调用一个协程并不会使其被调度执行

main()
<coroutine object main at 0x1053bb7c8>
  • 使用生成器实现协程
def coroutine_example():
   while True:
       x = yield
       print("Received:", x)
# 创建协程对象
coroutine = coroutine_example()
# 启动协程
next(coroutine)
# 发送数据给协程
coroutine.send("Hello")
  • 使用 async/await 语法

通过 async/await 语法来声明协程是编写 asyncio 应用的推荐方式。

import asyncio
async def async_function():
   print("Async function started")
   await asyncio.sleep(1)
   print("Async function completed")
# 运行异步函数
asyncio.run(async_function()) # asyncio.run() 函数用来运行最高层级的入口点 "main()" 函数 
  • 使用 asyncio 库提供的异步功能
import asyncio
async def async_task():
   print("Async task started")
   await asyncio.sleep(1)
   print("Async task completed")
# 创建事件循环
loop = asyncio.get_event_loop()
# 运行异步任务
loop.run_until_complete(async_task())
# 关闭事件循环
loop.close()
  • 并发运行多个协程即任务
## asyncio.create_task() 函数用来并发运行作为asyncio任务的多个协程。

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

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

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

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

创建任务

asyncio.create_task(coro, *, name=None, context=None)

coro 协程 封装为一个 Task 并调度其执行。返回 Task 对象。

name 不为 None,它将使用 Task.set_name() 来设为任务的名称。

可选的 context 参数允许指定自定义的 contextvars.Contextcoro 运行。 当未提供 context 时将创建当前上下文的副本。

该任务会在 get_running_loop() 返回的循环中执行,如果当前线程没有在运行的循环则会引发 RuntimeError

备注

asyncio.TaskGroup.create_task() 是一个平衡了结构化并发的新选择;它允许等待一组相关任务并具有极强的安全保证。

重要

保存一个指向此函数的结果的引用,以避免任务在执行过程中消失。 事件循环将只保留对任务的弱引用。 未在其他地方被引用的任务可能在任何时候被作为垃圾回收,即使是在它被完成之前。 如果需要可靠的“发射后不用管”后台任务,请将它们放到一个多项集中:

background_tasks = set()
for i in range(10):
    task = asyncio.create_task(some_coro(param=i))
    # Add task to the set. This creates a strong reference.
    background_tasks.add(task)
    # To prevent keeping references to finished tasks forever,
    # make each task remove its own reference from the set after
    # completion:
    task.add_done_callback(background_tasks.discard)

Added in version 3.7.

在 3.8 版本发生变更: 增加了 name 形参。

在 3.11 版本发生变更: 增加了 context 形参。

“您的支持是我持续分享的动力”

微信收款码
微信
支付宝收款码
支付宝

目录