前言
在现代编程中,异步编程已经成为一种非常重要的技术。Python的异步编程(Asynchronous Programming)是一种并发编程的方式,用于在单个线程内处理多个任务,从而提高程序的效率和响应速度。异步编程的核心思想是通过非阻塞的方式处理I/O操作,使得程序在等待I/O操作完成的同时可以继续执行其他任务。
什么时异步编程
异步编程是一种并发编程方式,旨在通过非阻塞的方式处理I/O操作。传统的同步编程在执行I/O操作时会阻塞整个程序,直到操作完成,而异步编程则允许程序在等待I/O操作完成的同时继续执行其他任务。这种机制可以显著提高程序的性能,特别是在处理大量I/O操作时。简单来说,异步就是发起一个请求后,不必等待请求的响应,就可以发起下一个请求,这一点和同步刚好相反。
Python中的异步编程
Python通过asyncio
模块提供了强大的异步编程支持。下面一起来看一下Python异步编程中的一些核心概念和技术:
async
和await
关键字
在Python中,async和await是用于定义异步函数和挂起点的关键字。一个函数如果使用了async关键字,就变成了一个协程(coroutine),可以在执行过程中被挂起和恢复。async和await可以让我们轻松实现异步编程,Python异步编程的基础就是协程。
import asyncio
async def say_hello():
print('start....')
await asyncio.sleep(1)
print('hello world!')
return 'good'
async def main():
result = await say_hello()
print(result)
asyncio.run(main())
输出结果:
start....
hello world!
good
上面就是一个简单的异步程序,say_hello()就是一个异步函数,await asyncio.sleep(1)
时被挂起,直到1秒钟后继续执行.
Python中使用异步编程就是如此的简单。可能你会说除了使用了async和await,以及使用asyncio.run转载函数后,其他的和普通函数好像没啥区别。你可以把main()函数当成普通函数使用看一下,控制台会报出一条提示**“RuntimeWarning: coroutine ‘main’ was never awaited”**,我的理解就是异步函数是需要一个驱动入口的,而asyncio.run就是这个入口,每一个使用async定义的函数都是一个协程,如果你了解经典协程,你就知道协程一般使用send()激活,异步编程使用async,await的底层实现也是基于经典协程(yield),我虽然没有查阅过源码,但我猜测asyncio.run应该也是调用了send函数,来激活协程。
asyncio
模块
asyncio是Python标准库中的一个模块,用于实现异步I/O。它提供了事件循环、任务、协程以及各种与I/O操作相关的功能。
import asyncio
async def say_hello():
await asyncio.sleep(1)
print("hello, World!")
async def main():
task = asyncio.create_task(say_hello())
await task
asyncio.run(main())
我们所使用的异步工具,基本上都能在asyncio中找到。
事件循环
事件循环(Event Loop)是异步编程的核心机制,它负责调度和执行异步任务。事件循环会不断检查和处理任务队列中的任务,确保它们能够在合适的时机被执行。在Python中,asyncio模块提供了一个强大的事件循环实现,理解事件循环,对于理解Python的异步编程非常有必要
事件循环的工作原理
事件循环的基本工作原理是通过一个无限循环不断地检查和执行任务队列中的任务。当任务队列中有任务时,事件循环会执行这些任务;当任务队列为空时,事件循环会进入休眠状态,直到有新的任务被加入任务队列。每一个线程都会维护着一个事件循环,所以我们不用考虑异步编程中数据同步的问题,同一时刻,在同一个线程中,不会同时处理两个异步函数的。
事件循环要注意下面三个关键点:
- 协程(Coroutine):使用async和await关键字定义的异步函数。
- 任务(Task):由事件循环调度和执行的协程。
- Future对象:表示异步操作的结果,可以用来等待操作完成。
在Python中,asyncio模块提供了事件循环的实现。
- 在Python3.8之前,使用
asyncio.get_event_loop()
函数获取当前事件循环,通过loop.run_until_complete()
方法运行异步任务。 - 在Python3.8之后,推荐使用
asyncio.get_running_loop
来处理事件循环,一般调用使用asyncio.gather()
函数。
import asyncio
async def say_hello():
await asyncio.sleep(1)
print("hello, world!")
async def main():
loop = asyncio.get_running_loop()
task = loop.create_task(say_hello())
await asyncio.gather(task)
asyncio.run(main())
在这个示例中,我们创建了一个简单的异步函数say_hello,它在等待1秒钟后打印"hello, world!"。然后,我们获取当前的事件循环并运行这个异步任务。
这是一个简单的事件循环的使用,希望你们自己下去展开一下事件循环的学习,事件循环还有很多高级的使用,比如处理多任务,还可以自定义事件循环。
异步任务
异步任务是由事件循环调度和执行的协程。可以使用asyncio.create_task()函数来创建任务,并通过事件循环运行这些任务。详细代码,可以参照上面。
并发运行多个任务
使用asyncio.gather()函数可以并发运行多个协程,并等待它们全部完成。
import asyncio
import time
from datetime import datetime
async def say_hello():
await asyncio.sleep(1)
print("hello, World!")
async def main():
await asyncio.gather(say_hello(),say_hello(),say_hello(),say_hello(),say_hello())
if __name__ == '__main__':
t1 = datetime.now()
asyncio.run(main())
t2 = datetime.now()
print(f'interval:{(t2-t1).total_seconds()}')
输出结果:
hello, World!
hello, World!
hello, World!
hello, World!
hello, World!
interval:1.018156
我们可以看到执行时间大约在1s,也就是说代码里面执行多个say_hello,并没有造成阻塞。
异步生成器和异步迭代器
异步生成器和异步迭代器允许我们在异步函数中使用yield
和await
关键字,从而创建可以异步迭代的数据流。
import asyncio
async def async_gen():
for i in range(5):
await asyncio.sleep(1)
yield i
async def main():
async for value in async_gen():
print(value)
asyncio.run(main())
上面代码中,会每隔1s,依次打印0,1,2,3,4
异步上下文管理器
使用async关键字可以定义异步上下文管理器,支持在async with语句中使用。
import asyncio
class AsyncCM:
async def __aenter__(self):
print("Entering context")
return self
async def __aexit__(self, exc_type, exc, tb):
print("Exiting context")
async def main():
async with AsyncCM():
print("Inside context")
asyncio.run(main())
我们在上下文管理器里面讲到过,上下文管理器是是实现了 __enter__和 __next__两个魔法函数;异步上下文管理器,就是实现了 __aenter__
和 __anext__
两个魔法函数
异步I/O操作
asyncio模块提供了对文件、网络连接等I/O操作的异步支持,使得这些操作在等待期间不会阻塞事件循环。
import asyncio
async def read_file():
loop = asyncio.get_event_loop()
with open('example.txt', 'r') as f:
data = await loop.run_in_executor(None, f.read)
print(data)
asyncio.run(read_file())
将同步代码长时间阻塞的异步函数添加到线程池
如果某个异步函数中,有长时间阻塞的同步代码,这是后如果在事件循环中调用,将阻塞整个事件循环,这种异步任务,我们可以交给线程池来处理
import asyncio
import time
from datetime import datetime
async def execute(i):
print(f'start task {i} ...')
await asyncio.sleep(i)
print(f'task {i} end')
async def test():
print('test start')
time.sleep(10)
await asyncio.sleep(1)
print('test end')
async def main():
# 并发运行异步任务
task1 = asyncio.create_task(execute(4))
task2 = asyncio.create_task(execute(2))
#task3 = asyncio.create_task(test())
# 在另一个线程中运行阻塞的同步函数
await asyncio.to_thread(test)
# 等待所有任务完成
await asyncio.gather(task1, task2)
if __name__ == '__main__':
# 运行主函数
asyncio.run(main())
你可以测试一下将test函数在asyncio.gather中执行的情况,看看和上面有何不同。
应用场景
Python的异步编程广泛应用于各种需要高并发和高性能的场景,如:
- 网络爬虫:处理大量的网络请求,提高数据抓取效率。
- Web服务器:如aiohttp,可以处理大量的并发请求,提高服务器的响应速度。
- 实时数据处理:如WebSocket连接,处理实时数据流。
结论
通过异步编程技术,您可以在Python中编写高效的并发程序,提高程序的响应速度和处理能力。无论是处理大量的I/O操作,还是需要高并发的应用场景,Python的异步编程都是一种非常强大的工具。本文只是Python异步编程的冰山一角,需要有更深层次的了解异步编程,还需要自己亲自去查阅文档。