事件循环工作原理
- 任务调度
- 在asyncio中,事件循环负责调度任务(
Task
)。任务是对协程的进一步封装,它包含了协程的执行状态等信息。事件循环维护一个任务队列,新创建的任务会被放入这个队列中。
- 事件循环按照一定的规则从任务队列中取出任务并执行。当一个任务执行到
await
语句时,它会暂停执行,事件循环会切换到其他可运行的任务。例如:
import asyncio
async def task1():
print('Task1 start')
await asyncio.sleep(1)
print('Task1 end')
async def task2():
print('Task2 start')
await asyncio.sleep(0.5)
print('Task2 end')
async def main():
task_list = [task1(), task2()]
await asyncio.gather(*task_list)
if __name__ == '__main__':
asyncio.run(main())
- 在上述代码中,
task1
和task2
是两个任务,asyncio.gather
将它们添加到事件循环的任务队列中,事件循环会调度它们执行,当遇到await asyncio.sleep
时,任务暂停,事件循环切换到其他任务。
- 协程管理
- 协程是异步编程的基础单元,通过
async def
定义。事件循环负责驱动协程的执行。当一个协程被await
时,事件循环会暂停该协程的执行,并将控制权交回给事件循环,以便执行其他协程。
- 协程可以通过
yield from
(Python 3.4 及以前)或await
(Python 3.5 及以后)暂停执行并等待另一个协程完成。例如,在task1
和task2
中,await asyncio.sleep
就是暂停协程执行并等待asyncio.sleep
这个协程完成。
- 处理I/O操作
- asyncio通过使用异步I/O库(如
aiohttp
用于HTTP请求,asyncpg
用于PostgreSQL数据库操作等)来实现高效的异步I/O。当遇到I/O操作(如网络请求、文件读取等)时,协程会通过await
暂停执行。
- 事件循环会将I/O操作注册到操作系统的I/O多路复用机制(如Linux上的
epoll
,Windows上的IOCP
)。当I/O操作完成时,操作系统会通知事件循环,事件循环再将对应的协程重新放入可运行队列,等待再次执行。例如,使用aiohttp
进行HTTP请求:
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://example.com')
print(html)
if __name__ == '__main__':
asyncio.run(main())
- 在上述代码中,
await session.get(url)
是一个I/O操作,协程会暂停,事件循环将这个I/O操作注册到I/O多路复用机制,当请求完成,事件循环唤醒协程继续执行。
实际项目场景应用
- 网络爬虫
- 在网络爬虫项目中,需要同时发起多个HTTP请求获取网页内容。如果使用同步方式,每个请求都要等待前一个请求完成,效率较低。使用asyncio可以并发地发起多个请求。例如,爬取多个网页的标题:
import aiohttp
import asyncio
async def fetch_title(session, url):
async with session.get(url) as response:
html = await response.text()
start = html.find('<title>') + len('<title>')
end = html.find('</title>')
return html[start:end]
async def main():
urls = ['http://example.com', 'http://python.org', 'http://github.com']
async with aiohttp.ClientSession() as session:
tasks = [fetch_title(session, url) for url in urls]
titles = await asyncio.gather(*tasks)
for title in titles:
print(title)
if __name__ == '__main__':
asyncio.run(main())
- 这里通过
asyncio.gather
并发执行多个fetch_title
任务,大大提高了爬虫效率。
- 实时数据处理
- 在一些实时数据处理场景,如实时监控系统,需要不断从多个数据源获取数据并处理。例如,从多个传感器(通过网络连接获取数据)实时读取数据并进行分析。使用asyncio可以在一个事件循环中高效地管理这些I/O操作和数据处理任务,避免阻塞,确保系统能够及时响应各个数据源的数据请求。