Python中实现协程的方法

本文介绍Python中协程的基本概念及其实现方式,包括使用yield关键字的手动切换方式和通过gevent模块实现的自动切换方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

协程:简单的来说就是不带有返回值的调用

最基本的实现方法:

  • 利用yeild来实现

import time

def work1(): # 循环打印数字1  

    while True:  

        print("-----1-----") # yield可以暂时挂起该函数,跳转到调用该函数的下方  

        yield # 延迟一秒以便观察  

        time.sleep(1)

def work2():     

    while True:  

        print("-----2-----")  

        yield  

        time.sleep(1)

th1 = work1()

th2 = work2()

while True: # 唤醒被挂起的函数  

    next(th1)  

    next(th2)

  • greenlet也可以实现了协程,但是这个还得人工切换,有点很麻烦,就不介绍了
  • python还有一个能够自动切换任务的模块 gevent 也能实现协程

import gevent

import time

def work1(): # 循环打印  

    while True:  

    print("----1----") # 破解sleep 使sleep不再阻塞  

    gevent.sleep(1)

def work2():  

    while True:  

    print("----2----")  

    gevent.sleep(1)

# 创建并开始执行携程

th1 = gevent.spawn(work1)

th2 = gevent.spawn(work2)

# 阻塞等待携程结束

gevent.joinall([th1,th2])

<think>我们正在讨论的是协程间的双向通信。根据引用内容,特别是引用[1]中的示例,我们可以看到两种不同的方式:一种是基于生成器的协程(使用yield),另一种是现代异步协程(使用async/await)。在Python中,协程间的双向通信可以通过以下几种方式实现: 1. **使用基于yield的协程(传统方式)**:通过`yield`表达式既可以接收数据,也可以发送数据。在引用[1]的示例中,`echo`函数就是一个简单的例子:`received = yield` 这里yield可以接收外部通过send()发送的数据,同时也可以返回数据(通过下一个yield)。 2. **使用async/await和队列(现代方式)**:在异步编程中,协程之间通常通过消息队列(如asyncio.Queue)进行通信。一个协程将数据放入队列,另一个协程从队列中取出数据,从而实现双向通信。 3. **使用回调函数**:引用[4]中展示了通过add_done_callback给协程添加回调函数,但这主要用于获取协程的结果,并不是实时的双向通信。 4. **使用事件循环和Future对象**:引用[3]和[4]中提到了事件循环和Future对象,我们可以利用Future对象来传递数据,但通常用于单向结果传递。 5. **使用同步原语**:引用[5]提到了锁、条件变量和事件等同步原语,这些可以用于协程间的协调,但本身不直接用于数据通信,而是用于控制共享资源的访问。 下面,我们将重点介绍两种最常用的方法:基于yield的传统协程通信和基于async/await的异步协程通信(使用队列)。 ### 方法1:基于yield的传统协程双向通信 在生成器协程中,我们可以通过`send()`方法协程发送数据,同时协程通过`yield`返回数据。注意,这种协程需要先通过`next()`或`send(None)`来启动(或称为“预激”)。 **示例代码**: ```python def coroutine1(): print("协程1启动") while True: # 等待接收数据,并返回一个消息 received = yield f"协程1收到: {None}" # 第一次启动时,yield返回右侧的表达式,但此时还未接收到数据 # 注意:第一次调用next()时,执行到yield处暂停,返回右侧的值。然后调用send(data)时,data赋值给received,并继续执行到下一个yield print(f"协程1收到: {received}") # 返回一个响应 response = f"处理后的: {received}" received = yield response # 再次yield,将响应返回,并等待下一次数据 # 使用示例 c1 = coroutine1() # 启动协程 initial_response = next(c1) # 输出: 协程1启动,并返回第一个yield右侧的值(此时还未发送数据) print(initial_response) # 输出: "协程1收到: None" # 发送数据并接收响应 response1 = c1.send("Hello") print(response1) # 输出: "处理后的: Hello" # 再次发送数据 response2 = c1.send("World") print(response2) # 输出: "处理后的: World" ``` 但是,这种方法通常用于一个协程与调用者之间的双向通信。如果要在两个协程之间进行双向通信,我们需要一个中间调度器(如事件循环)来协调。在传统生成器协程中,实现两个协程直接通信比较困难,通常需要调度器转发。 ### 方法2:使用asyncio.Queue实现异步协程间的双向通信 在异步编程中,我们可以使用队列(asyncio.Queue)作为协程间的通信桥梁。每个协程可以拥有自己的输入队列和输出队列,从而实现双向通信。 **示例场景**:创建两个协程,一个用于处理请求(Processor),另一个用于处理响应(Responder)。它们之间通过两个队列进行通信:请求队列和响应队列。 **示例代码**: ```python import asyncio async def producer(queue_in, queue_out): """生产者协程:发送请求并等待响应""" for i in range(3): # 发送请求 await queue_in.put(f"请求-{i}") print(f"生产者发送: 请求-{i}") # 等待响应 response = await queue_out.get() print(f"生产者收到响应: {response}") # 模拟处理时间 await asyncio.sleep(0.1) async def consumer(queue_in, queue_out): """消费者协程:处理请求并返回响应""" while True: # 等待请求 request = await queue_in.get() print(f"消费者收到: {request}") # 处理请求并生成响应 response = f"响应-{request.split('-')[1]}" # 将响应放入响应队列 await queue_out.put(response) print(f"消费者发送: {response}") # 标记任务完成 queue_in.task_done() async def main(): # 创建两个队列:请求队列和响应队列 request_queue = asyncio.Queue() response_queue = asyncio.Queue() # 创建消费者任务(持续运行) consumer_task = asyncio.create_task(consumer(request_queue, response_queue)) # 运行生产者 await producer(request_queue, response_queue) # 等待请求队列处理完成(确保所有请求都被处理) await request_queue.join() # 取消消费者任务(因为消费者是无限循环,所以需要取消) consumer_task.cancel() # 等待消费者任务结束 await consumer_task # 运行主函数 asyncio.run(main()) ``` **代码说明**: - 我们创建了两个队列:`request_queue`(请求队列)和`response_queue`(响应队列)。 - `producer`协程(生产者)将请求放入请求队列,然后从响应队列中获取响应。 - `consumer`协程(消费者)从请求队列中获取请求,处理后将响应放入响应队列。 - 这样,生产者和消费者之间就通过两个队列实现了双向通信。 **输出示例**: ``` 生产者发送: 请求-0 消费者收到: 请求-0 消费者发送: 响应-0 生产者收到响应: 响应-0 生产者发送: 请求-1 消费者收到: 请求-1 消费者发送: 响应-1 生产者收到响应: 响应-1 生产者发送: 请求-2 消费者收到: 请求-2 消费者发送: 响应-2 生产者收到响应: 响应-2 ``` ### 方法3:使用管道(Pipes)或通道(Channels) 在更复杂的场景中,我们可以设计类似于管道的结构,其中每个协程都有一组输入和输出通道。但Python标准库中没有直接提供这样的高级抽象,我们可以用队列来构建。 ### 注意事项 1. **避免死锁**:在使用队列时,要注意协程间的依赖关系,避免出现循环等待。 2. **队列大小**:队列可以设置最大长度,防止内存溢出。 3. **异常处理**:在协程中妥善处理异常,避免整个程序崩溃。 ### 总结 在Python实现协程间的双向通信,主要方法有: - **传统生成器协程**:通过`yield`和`send()`实现,但适用于一对一的通信,且需要手动管理协程的启动和迭代。 - **异步队列(asyncio.Queue)**:在现代异步编程中,使用队列作为协程间的通信桥梁,这是最常用且灵活的方式。 对于现代异步编程,推荐使用asyncio.Queue来实现协程间的双向通信,因为它清晰、灵活且易于管理[^1][^3]。 §§相关问题§§ 1. 如何避免使用队列时出现死锁? 2. asyncio.Queue与多线程队列有何区别? 3. 除了队列,还有哪些协程间通信的方法?</think>### Python协程间双向通信的实现方法 协程间的双向通信是异步编程的核心能力,允许协程在挂起和恢复时交换数据。以下是Python实现协程间双向通信的三种主要方法: --- #### 一、使用`yield`表达式实现双向通信(传统方式) 通过`yield`表达式同时接收和发送数据,这是生成器协程的基础通信机制。 **实现原理**: ```python def coroutine(): # 第一阶段:发送数据并等待接收 received1 = yield "发送数据A" # ① 发送"发送数据A",暂停等待输入 # 第二阶段:处理接收的数据并返回新数据 received2 = yield f"处理结果: {received1}" # ② 发送处理结果,再次暂停 # 第三阶段:最终处理 yield f"最终结果: {received2.upper()}" # ③ 发送最终结果 ``` **使用示例**: ```python # 创建协程 coro = coroutine() # 启动协程(必须先用None或next()初始化) first_output = next(coro) # 输出: "发送数据A" [^1] # 发送数据并获取响应 second_output = coro.send("输入数据B") # 输出: "处理结果: 输入数据B" [^1] # 再次发送数据 final_output = coro.send("输入数据C") # 输出: "最终结果: 输入数据C" ``` **关键点**: 1. 每次`yield`同时完成发送和接收操作 2. 协程通过`send(value)`接收外部数据 3. 必须先用`next()`初始化协程到第一个`yield`位置[^1] --- #### 二、使用`asyncio.Queue`实现异步通信(推荐方式) 现代异步编程中,队列是协程间通信最安全高效的方式,特别适合生产者-消费者模式。 **实现原理**: ```python import asyncio async def producer(queue): for i in range(3): await queue.put(f"消息-{i}") # 发送数据 print(f"生产者发送: 消息-{i}") await asyncio.sleep(0.1) async def consumer(queue): while True: data = await queue.get() # 接收数据 print(f"消费者收到: {data}") queue.task_done() # 标记任务完成 ``` **使用示例**: ```python async def main(): queue = asyncio.Queue(maxsize=5) # 创建带缓冲的队列 # 启动生产者和消费者 producer_task = asyncio.create_task(producer(queue)) consumer_task = asyncio.create_task(consumer(queue)) # 等待生产者完成 await producer_task # 等待队列清空 await queue.join() # 取消消费者任务 consumer_task.cancel() asyncio.run(main()) # 执行[^3] ``` **输出结果**: ``` 生产者发送: 消息-0 消费者收到: 消息-0 生产者发送: 消息-1 消费者收到: 消息-1 生产者发送: 消息-2 消费者收到: 消息-2 ``` **优势**: 1. 线程安全的通信机制 2. 支持背压控制(通过`maxsize`参数) 3. 天然支持一对多、多对多通信模式 4. 与事件循环无缝集成[^3] --- #### 三、使用`Event`和`Condition`实现状态同步 当需要基于特定条件协调协程时,可以使用同步原语: ```python import asyncio async def waiter(event): print("等待器进入等待状态") await event.wait() # 等待事件触发 print("等待器被唤醒,执行任务") async def setter(event): await asyncio.sleep(1) print("触发器设置事件") event.set() # 触发事件 async def main(): event = asyncio.Event() await asyncio.gather(waiter(event), setter(event)) asyncio.run(main()) ``` **输出**: ``` 等待器进入等待状态 (1秒后) 触发器设置事件 等待器被唤醒,执行任务 ``` --- ### 通信模式对比 | 方法 | 适用场景 | 优势 | 局限性 | |---------------|----------------------------|-------------------------------|------------------------| | `yield/send` | 简单的一对一通信 | 无需额外依赖,语法简单 | 难以扩展,手动管理状态 | | `asyncio.Queue` | 生产者-消费者模式 | 线程安全,支持背压控制 | 需要事件循环环境 | | `Event/Condition` | 基于条件的协调 | 精确控制执行流程 | 不直接传输数据 | --- ### 最佳实践建议 1. **简单场景**:使用`yield/send`快速实现双向通信 2. **生产环境**:优先选择`asyncio.Queue`保证通信安全 3. **复杂协调**:结合`Event`+`Queue`实现状态驱动的通信 4. **避免共享状态**:使用通信代替共享变量,遵循CSP并发模型 5. **异常处理**:使用`try/finally`确保资源释放: ```python async def safe_coroutine(queue): try: while True: data = await queue.get() # 处理数据 finally: print("清理资源") # 确保资源释放[^4] ``` > 在WebSocket等持久连接场景中,Tornado等框架已封装了协程通信机制,可直接使用其内置类实现高效双向通信[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大、大摩王

你的鼓励是我创装的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值