提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:本文主要提到python中异步并发操作,关于Thread多线程同步并发见上文。
Python 中的协程函数也就是我们所说的异步编程,它允许你编写非阻塞的代码,特别适用于 I/O 密集型任务(如网络请求、文件读写等)。首先需要清楚的明白如下概念:
- 同步编程
像我们正常定义的函数就是同步函数,它只能每个函数依次进行,假如某个函数遇到了阻塞,那么整个程序将会被堵塞住,影响执行效率。当然我们可以通过多线程的方式处理,多线程处理的函数也是同步函数,他们将会在同一时刻同步并发。 - 异步编程
异步编程也就是我们今天说的协程,它是在一个单一线程里执行的。它的概念是同一个时间段完成多个任务(就像我们人类一样,需要执行吃饭任务,在点完外卖后,不会一直等待外卖,而是这个时间去打一把游戏,等外卖到了,再继续执行吃饭任务,显然这更加的合理。)这就是所谓的异步并发。
提示:以下是本篇文章正文内容,仅供学习参考
一、asyncio是什么?
asyncio 是 Python 标准库中的一个模块,用于编写异步 I/O 操作的代码。它基于事件循环(Event Loop)和协程(Coroutine),能够高效地处理 I/O 密集型任务(如网络请求、文件读写等),同时避免阻塞主线程。
二、使用步骤
1.引入库
代码如下(示例):
import asyncio
2. 定义协程函数 async
代码如下(示例):
import asyncio
import time
async def task_1():
print("send a task1 request...")
await asyncio.sleep(5)
print("get responds")
return "task1"
async def task_2():
print("send a task2 request...")
await asyncio.sleep(3)
print("get responds")
return "task2"
if __name__ == '__main__':
t1 = task_1()
t2 = task_2()
print(f"t1: {t1}, t2: {t2}")
async 关键字定义的函数为协程函数,返回一个协程对象,执行结果打印如下,得到的t1和t2是协程对象,结果如下

3. 执行协程函数 await
await 关键字是用来执行协程函数对象的,await 会暂停执行当前协程直到等待的协程执行完毕,并且让出控制权给事件循环,见示例代码:
import asyncio
import time
async def task_1():
print("send a task1 request...")
await asyncio.sleep(2)
print("get task1 responds")
return "task1"
async def task_2():
print("send a task2 request...")
await asyncio.sleep(3)
print("get task2 responds")
return "task2"
def task3():
print("running task 3")
time.sleep(3)
print("end task3")
async def _main():
ret = await task_1()
ret = await task_2()
task3()
if __name__ == '__main__':
start_ = time.perf_counter()
asyncio.run(_main())
end_ = time.perf_counter()
print(f"total : {end_ - start_}")
执行结果如下:

从输出结果看任务是顺序执行,运行时间也是接近8 s,那是因为await 的作用是等待后面协程执行结束,在示例代码中task1()、task2() 依次执行,直到结束才会执行下一个,也就是说在此的任务是串行的,适合前后任务有依赖的场景。
获取你会感到疑惑,如果止步于此,为何要大费周章的引入异步函数呢,普通函数不是也是逐行执行吗?await 和普通函数的执行方式确实有一些相似之处,但它们的关键区别在于是否会阻塞事件循环,以及 是否允许并发执行其他任务。我们对上面代码做如下修改:
import asyncio
import time
async def task_1():
print("send a task1 request...")
await asyncio.sleep(2)
print("get task1 responds")
return "task1"
async def task_2():
print("send a task2 request...")
await asyncio.sleep(3)
print("get task2 responds")
return "task2"
def task3():
print("running task 3")
time.sleep(3)
print("end task3")
async def _main():
task1 = asyncio.create_task(task_1())
task2 = asyncio.create_task(task_2())
await task1
await task2
task3()
if __name__ == '__main__':
start_ = time.perf_counter()
asyncio.run(_main())
end_ = time.perf_counter()
print(f"total : {end_ - start_}")
运行结果如下:

可见运行时间变短了,因为task1和task2是异步并发的,代码中通过create_task方法创建任务,再用await关键字,task2并没有等待task1执行完毕再执行,两者是并发关系。
这里的执行顺序如下:
- 执行
task1遇到await asyncio.sleep(2),暂停执行, 把控制权给事件循环。 - 事件循环开始执行
task2也遇到await asyncio.sleep(3), 暂停执行,交出控制权。 - 事件循环发现
task1中await asyncio.sleep(2)结束了,恢复运行task1。 - 事件循环再恢复运行
task2, 最后再正常等待两个任务执行完毕
所以如上场景适用于task1和task2没有依赖,做到了并发的操作,所以await的核心就是让出控制权给事件循环,让事件循环调度所有的协程函数。
注意:同样的我们还可以在task1中调用协程任务task4, task5, 同样遵循上面的规律串行或并行。对于task3,事件循环也会等待task1和task2执行完毕后再继续执行。
对于并行所有任务还有另外一种简单的写法,效果是一样的如下:
async def _main():
await asyncio.gather(task_1(), task_2())
task3()
4. 事件循环 (Event Loop)
事件循环(Event Loop) 是异步编程的核心机制,它负责调度和执行异步任务(如协程),并管理 I/O 操作、定时器、回调等。事件循环使得程序能够在等待 I/O 操作(如网络请求、文件读写)时不会阻塞,从而高效地处理并发任务。
事件循环主要作用是负责任务的调度,开启,停止,恢复,关闭,这些操作我们都可以通过asyncio.run(_main()),这里的_main是调度的函数。如上面的代码,就是通过这种方式创建事件循环的。
除了通过asyncio.run的方式自动创建和关闭事件循环,还可以手动创建事件循环,如下:
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(1)
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
# 获取事件循环
loop = asyncio.get_event_loop()
try:
# 运行任务
loop.run_until_complete(task1())
loop.run_until_complete(task2())
finally:
loop.close()
稍加修改同步处理任务
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(1)
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
# 获取事件循环
loop = asyncio.get_event_loop()
try:
# 运行任务
loop.run_until_complete(asyncio.gather(task1(), task2()))
finally:
loop.close()
除了通过asyncio.run自动创建和关闭事件循环,还可以手动管理,代码如下:
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(1)
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
# 创建事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
# 运行任务
loop.run_until_complete(asyncio.gather(task1(), task2()))
finally:
loop.close()
asyncio.new_event_loop() 创建一个事件循环,然后事件循环运行一个任务,最后关闭事件循环。当然还可以直接获得一个当前的事件循环,假如当前没有事件循环,就会自动创建,代码如下:
import asyncio
async def task1():
print("Task 1 started")
await asyncio.sleep(1)
print("Task 1 finished")
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
# 创建事件循环
loop = asyncio.get_event_loop()
try:
# 运行任务
loop.run_until_complete(asyncio.gather(task1(), task2()))
finally:
loop.close()
注意: 通常一个线程里面只允许存在一个事件循环,假如存在多个事件循环运行,会抛出异常。
另外,在一个事件循环里面,开始运行了一个任务后,需要等待这个任务执行结束,才可以重新调用 loop.run_until_complete, 这个函数是不允许嵌套使用的,否则会出现loop is running 的错误信息,如下面代码:
import asyncio
async def task1():
loop = asyncio.get_event_loop()
async def task3():
print("task3")
task3 = loop.create_task(task3())
loop.run_until_complete(task3)
async def task2():
print("Task 2 started")
await asyncio.sleep(2)
print("Task 2 finished")
async def _main():
await task1()
await task2()
# 创建事件循环
if __name__ == '__main__':
loop = asyncio.get_event_loop()
_main = loop.create_task(_main())
loop.run_until_complete(_main)
loop.close()
输出报错信息:

5. 多线程中使用协程 run_coroutine_threadsafe()
前面说了,协程是为单线程服务的,每一个线程中只存在一个事件循环,当然也是可以在多线程里启动多个事件循环的,见如下代码:
import asyncio
import threading
import time
# 定义一个协程任务
async def task(name):
print(f"{name} 开始")
await asyncio.sleep(1)
print(f"{name} 结束")
# 启动事件循环的函数
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
# 创建两个事件循环
loop1 = asyncio.new_event_loop()
loop2 = asyncio.new_event_loop()
# 创建两个线程,每个线程运行一个事件循环
thread1 = threading.Thread(target=start_loop, args=(loop1,), daemon=True)
thread2 = threading.Thread(target=start_loop, args=(loop2,), daemon=True)
# 启动线程
thread1.start()
thread2.start()
# 在主线程中调度任务到不同线程的事件循环
asyncio.run_coroutine_threadsafe(task("任务 1"), loop1)
asyncio.run_coroutine_threadsafe(task("任务 2"), loop2)
# 主线程等待一段时间,确保子线程中的任务完成
time.sleep(3)
上面代码在主线程里创建两个事件循环,然后创建两个守护线程,然后再把任务通过run_coroutine_threadsafe()执行,注意事件循环会一直运行,但是由于守护线程的因素,当程序运行结束,整个线程退出。
我们也可以接收run_coroutine_threadsafe()的返回对象,获取任务运行结果。
import asyncio
import threading
async def task(name, delay):
return f"{name} 结果"
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
# 创建事件循环并启动线程
loop = asyncio.new_event_loop()
thread = threading.Thread(target=start_loop, args=(loop,), daemon=True)
thread.start()
# 调度任务并获取结果
future = asyncio.run_coroutine_threadsafe(task("任务 1", 1), loop)
print(future.result()) # 阻塞等待任务完成并获取结果
注意:这里的future.result()方法是阻塞的,只有当结果返回后程序才会结束,这也保证了线程安全的执行完。
另外还可以通过future.done()判断任务执行状态,代码如下:
# 调度任务并获取结果
future = asyncio.run_coroutine_threadsafe(task("任务 1", 1), loop)
while not future.done():
print("任务正在执行")
print(future.result()) # 阻塞等待任务完成并获取结果
还可以调用强行停止事件循环方法call_soon_threadsafe,此时不管协程是否执行结束,事件循环都会被关闭:
import asyncio
import threading
import time
async def task(name, delay):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 结束")
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
# 创建事件循环并启动线程
loop = asyncio.new_event_loop()
thread = threading.Thread(target=start_loop, args=(loop,))
thread.start()
# 调度任务
asyncio.run_coroutine_threadsafe(task("任务 1", 3), loop)
time.sleep(5)
# 停止事件循环
loop.call_soon_threadsafe(loop.stop)
# 等待线程结束
thread.join()
总结
以上就是今天要讲的内容,本文介绍了协程函数和事件循环的使用,对于绝大部分场合,使用ayncio.run()管理事件循环是最好的选择。
1104

被折叠的 条评论
为什么被折叠?



