目录
1)如何在多进程中使用协程(启用一个子进程),并确保能够根据信号正常结束。
2)如何在多个子进程中分别运行不同的协程任务,并确保它们能够根据信号正常结束。
8、跨线程调度:asyncio.run_coroutine_threadsafe
1)使用默认的线程池:如果不指定执行器(executor),那么 asyncio 会使用一个内部的线程池来执行传递给它的函数。
10、run_in_executor和run_coroutine_threadsafe有啥区别
2、举个例子:I/O密集型任务(如网络请求、文件读写),如下载一百万张图片,多线程、多进程、协程如何选择,各自的优势、缺点、瓶颈
接上篇,一文掌握python协程asyncio(一)-优快云博客
六、进阶功能
5、多线程与协程结合
结合多线程和协程的一个常见用途是创建一个既能够处理高并发I/O操作,又能在需要时利用多核CPU进行计算的程序。下面是一个简单的示例,展示了如何在一个多线程环境中使用asyncio
来驱动并发的I/O操作,同时在每个线程内部使用单独的事件循环来管理协程。
import asyncio
import threading
async def my_coroutine(id, thread_name):
print(f"Starting coroutine {id} in thread {thread_name}...")
await asyncio.sleep(1) # 模拟异步I/O操作,等待1秒
print(f"Ending coroutine {id} in thread {thread_name}...")
def run_coroutines(thread_name):
# 初始化并设置事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 创建协程任务并运行
tasks = [loop.create_task(my_coroutine(i, thread_name)) for i in range(3)]
loop.run_until_complete(asyncio.wait(tasks))
# 关闭事件循环
loop.close()
print(f"Event loop in thread {thread_name} closed.")
def main():
threads = []
for i in range(2): # 创建并启动两个线程
thread_name = f"Thread-{i + 1}"
t = threading.Thread(target=run_coroutines, args=(thread_name,))
t.start()
threads.append(t)
print(f"Started thread {thread_name}.")
# 等待所有线程执行完毕
for t in threads:
t.join()
print(f"Joined thread {t.name}.")
if __name__ == "__main__":
main()
在上面的示例中,创建了两个线程,每个线程都运行一个事件循环并执行三个协程。由于协程是协作式的,它们不会真正并行执行(在同一个线程内),但在多线程环境中,不同的线程可以并行执行不同的协程。这样,当某些协程等待I/O操作完成时,其他线程中的协程可以继续执行,从而提高了整体效率。
然而,需要注意的是,过度使用多线程和协程可能会增加程序的复杂性和调试难度。在设计并发程序时,应该根据具体的应用场景和需求来选择最适合的并发模型。
6、多进程与协程结合
1)如何在多进程中使用协程(启用一个子进程),并确保能够根据信号正常结束。
在Python中,实现跨进程的异步通信和协作,同时在多进程中使用协程,需要利用进程间的通信机制(如管道、队列、共享内存等)与事件循环的隔离性相结合。使用multiprocessing.Event
来同步停止信号,并在子进程中直接使用loop.run_until_complete()
来运行协程直到收到停止信号,避免了事件循环的无限循环问题。
import asyncio
import multiprocessing
import time
async def my_coroutine(stop_event):
# is_set(): 返回内部标志的状态,如果标志被设置(True),则返回True,否则返回False。
while not stop_event.is_set():
print("Coroutine running")
await asyncio.sleep(1)
print("Coroutine stopped")
def start_loop(stop_event):
loop = asyncio.new_event_loop() # 创建新的事件循环
asyncio.set_event_loop(loop) # 设置当前线程的事件循环
try:
# 在事件循环中运行协程直到完成
loop.run_until_complete(my_coroutine(stop_event))
finally:
loop.stop() # 停止事件循环
loop.close() # 关闭事件循环
if __name__ == "__main__":
# 创建一个用于控制协程停止的事件
stop_event = multiprocessing.Event()
# 开始一个新的进程来运行协程
process = multiprocessing.Process(target=start_loop, args=(stop_event,))
process.start()
time.sleep(5) # 让协程运行一段时间
print("Set stop event.")
# 设置停止事件,通知协程结束
stop_event.set() # 设置内部标志为True
# 等待子进程结束
process.join()
print("All done.")
这个例子中,使用了multiprocessing.Event
来传递停止信号,这是一个支持跨进程通信的同步原语。start_loop
函数中,我们通过loop.run_until_complete(my_coroutine(stop_event))
来等待协程直到它自然结束(即stop_event.is_set()
变为True),这样应该能避免事件循环无限运行的问题,并在设置停止信号后正常结束进程。
2)如何在多个子进程中分别运行不同的协程任务,并确保它们能够根据信号正常结束。
每个进程需要有自己的事件循环来管理协程,以下是一个示例,展示了如何在多个子进程中分别运行不同的协程任务,并确保它们能够根据信号正常结束:
import asyncio
import multiprocessing
import time
async def my_coroutine(name, stop_event):
counter = 0
while not stop_event.is_set():
print(f"{name} running, counter: {counter}")
await asyncio.sleep(1)
counter += 1
print(f"{name} stopping...")
def start_loop(name, stop_event):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
# 直接运行协程,并监视停止事件
loop.run_until_complete(my_coroutine(name, stop_event))
finally:
loop.stop()
loop.close()
if __name__ == "__main__":
# 创建两个事件,分别用于控制两个子进程的停止
stop_event1 = multiprocessing.Event()
stop_event2 = multiprocessing.Event()
# 启动两个子进程,每个进程运行一个不同的协程
process1 = multiprocessing.Process(target=start_loop, args=("Coroutine 1", stop_event1,))
process2 = multiprocessing.Process(target=start_loop, args=("Coroutine 2", stop_event2,))
process1.start()
process2.start()
time.sleep(10) # 让协程运行一段时间
print("Setting stop events...")
stop_event1.set() # 设置第一个子进程的停止事件
time.sleep(2) # 等待一段时间,以便观察第一个协程停止
stop_event2.set() # 设置第二个子进程的停止事件
# 确保所有进程结束
process1.join()
process2.join()
print("All processes done.")
这个示例中,定义了两个协程my_coroutine
,分别在两个子进程中运行,并通过各自的stop_event
来控制何时结束。主进程等待一段时间后,分别设置这两个事件,通知对应的子进程中的协程停止运行。每个子进程拥有独立的事件循环,保证了它们可以并发执行且互不影响。
3)更好的方案,结合进程池和协程来实现更高效的任务管理
考虑到concurrent.futures.ProcessPoolExecutor
本身不直接支持协程,采用一种折衷的方法:在每个子进程中手动创建事件循环,并通过进程池来管理这些子进程。这里的关键是通过进程间通信来传递停止信号,而不是直接在协程中使用事