Python中使用yield实现协程调度

要利用 yield 实现一个好用的协程,首先需要理解协程的基本概念:它是能够挂起(暂停)和恢复执行的函数,可以在不同的地方进行任务切换。这种特性使得协程非常适合 I/O 密集型任务和并发任务的调度。

在 Python 中,生成器(通过 yield)是实现协程的一种方式。要实现一个好用的协程,关键在于以下几个方面:

  1. 暂停和恢复执行:协程能够在执行过程中挂起,等待其他任务的完成,再继续执行。yieldsend() 是这类协作的关键。
  2. 双向通信:协程不仅能够返回数据,还能接收外部数据。
  3. 任务调度:利用协程,可以调度多个任务进行并发执行。Python 的 asyncio 库实际上是基于协程的实现,但在不使用 asyncio 的情况下,您也可以利用 yield 来手动调度任务。

实现一个基本的协程

下面是一个基于 yield 实现的简单协程示例,它模拟了一个并发任务调度器。

示例:实现一个简单的协程调度器

这个例子中,我们将实现一个调度器,它会调度多个任务并逐一执行,每个任务会在某个点暂停(通过 yield),然后由调度器恢复。

import time

# 协程任务1
def task1():
    print("Task 1 started")
    time.sleep(1)  # 模拟I/O操作
    yield  # 挂起任务
    print("Task 1 resumed")

# 协程任务2
def task2():
    print("Task 2 started")
    time.sleep(2)  # 模拟I/O操作
    yield  # 挂起任务
    print("Task 2 resumed")

# 调度器:负责调度任务并让它们交替执行
def scheduler(*tasks):
    while tasks:
        task = tasks.pop(0)  # 获取一个任务
        try:
            next(task)  # 启动任务,执行到第一个 yield
            tasks.append(task)  # 任务没有完成,放回队列继续执行
        except StopIteration:
            # 任务完成,移出队列
            pass

if __name__ == '__main__':
    # 初始化任务
    t1 = task1()
    t2 = task2()
    
    # 启动调度器
    scheduler(t1, t2)

解释

  1. task1() 和 task2(): 这两个函数是协程(生成器),它们在执行过程中会通过 yield 暂停执行,模拟了 I/O 操作。
  2. scheduler(): scheduler 是一个简单的调度器,它接收多个协程任务,并轮流执行它们。 调度器通过调用 next(task) 启动任务,每次执行到 yield 时,任务会暂停,调度器将任务重新放回队列等待下一次执行。
  3. 任务切换: yield 会使得协程挂起,而调度器的任务切换机制会使得多个任务能交替进行,模拟了并发执行的效果。
  4. StopIteration: 当一个协程任务完成时(没有更多的 yield 语句),会抛出 StopIteration 异常,调度器将捕获此异常并移除任务。

输出:

Task 1 started
Task 2 started
Task 1 resumed
Task 2 resumed

改进:增加返回值和任务管理

为了让协程更加实用,您可以加入返回值,任务管理(比如任务状态),以及通过外部输入控制协程的执行。以下是一个更复杂的版本,它允许协程返回结果,并通过外部 send() 方法传递输入:

import time

# 协程任务1
def task1():
    print("Task 1 started")
    yield 1  # 任务挂起,返回1
    print("Task 1 resumed")
    return "Task 1 done"

# 协程任务2
def task2():
    print("Task 2 started")
    yield 2  # 任务挂起,返回2
    print("Task 2 resumed")
    return "Task 2 done"

# 调度器:负责调度任务并让它们交替执行
def scheduler(*tasks):
    while tasks:
        task = tasks.pop(0)  # 获取一个任务
        try:
            result = next(task)  # 启动任务,执行到第一个 yield
            print(f"Task yielded: {result}")
            tasks.append(task)  # 任务没有完成,放回队列继续执行
        except StopIteration as e:
            # 任务完成,移出队列,并返回任务的结果
            print(f"Task completed with result: {e.value}")

if __name__ == '__main__':
    # 初始化任务
    t1 = task1()
    t2 = task2()
    
    # 启动调度器
    scheduler(t1, t2)

解释改进点

  1. yield 返回值
  2. StopIteration 异常处理
  3. 任务管理

输出:

Task 1 started
Task 2 started
Task yielded: 1
Task yielded: 2
Task 1 resumed
Task completed with result: Task 1 done
Task 2 resumed
Task completed with result: Task 2 done

总结

通过 yield 实现的协程具有以下优势:

  • 暂停和恢复:可以在执行过程中暂停,保存执行状态,等待某些事件(如 I/O 操作)完成后再恢复执行。
  • 双向通信:可以通过 send() 发送数据到协程中,形成交互式的编程模式。
  • 任务调度:可以手动管理任务的执行,控制任务切换,模拟并发执行。

这种基于 yield 的协程实现方法非常适用于 I/O 密集型的任务(如网络请求、文件操作等),并且能高效地调度多个任务,避免了传统线程/进程带来的高开销。


文章标签:

评论(0)