Python asyncio高级编程:事件循环与并发控制
引言
Python的asyncio库为异步编程提供了强大的支持,通过事件循环、协程和任务调度,能够高效处理IO密集型任务。本文将深入探讨asyncio的高级特性,包括事件循环的精细控制、普通函数的调度执行、协程同步机制以及实战案例,帮助开发者掌握异步编程的核心技巧,构建高效的并发应用。
一、事件循环(Event Loop)深度解析
事件循环是asyncio的核心,负责调度和执行协程、回调函数和IO操作。理解事件循环的工作原理和高级操作是掌握asyncio的关键。
1.1 事件循环的生命周期
事件循环的典型生命周期包括创建、配置、运行和关闭四个阶段:
import asyncio
import logging
# 配置日志,便于调试
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def sample_coroutine():
logger.info("Coroutine is running")
await asyncio.sleep(1)
logger.info("Coroutine completed")
return "Result"
def main():
# 1. 创建事件循环
loop = asyncio.get_event_loop()
try:
# 2. 配置事件循环(可选)
loop.set_debug(True) # 启用调试模式
# 3. 运行事件循环
logger.info("Starting event loop")
result = loop.run_until_complete(sample_coroutine())
logger.info(f"Coroutine result: {result}")
finally:
# 4. 关闭事件循环
loop.close()
logger.info("Event loop closed")
if __name__ == "__main__":
main()
Python 3.7+提供了更简洁的asyncio.run()函数,自动管理事件循环的创建和关闭:
async def main_coroutine():
logger.info("Coroutine is running")
await asyncio.sleep(1)
logger.info("Coroutine completed")
return "Result"
if __name__ == "__main__":
result = asyncio.run(main_coroutine())
logger.info(f"Coroutine result: {result}")
1.2 无限循环任务
使用run_forever()方法可以启动一个无限运行的事件循环,直到显式调用loop.stop():
1.2.1 单任务无限循环
import asyncio
from datetime import datetime
async def periodic_task(interval):
"""周期性任务"""
while True:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{now}] Periodic task running")
await asyncio.sleep(interval)
def stop_loop_after(loop, delay):
"""延迟后停止事件循环"""
def stop():
print(f"Stopping loop after {delay} seconds")
loop.stop()
loop.call_later(delay, stop)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# 添加周期性任务
asyncio.ensure_future(periodic_task(2)) # 每2秒执行一次
# 设置5秒后停止循环
stop_loop_after(loop, 5)
try:
print("Starting event loop...")
loop.run_forever()
finally:
loop.close()
print("Event loop closed")
1.2.2 多任务协调
import asyncio
from datetime import datetime
import functools
async def task1():
print(f"[{datetime.now()}] Task 1 started")
await asyncio.sleep(3)
print(f"[{datetime.now()}] Task 1 completed")
return "Task 1 result"
async def task2():
print(f"[{datetime.now()}] Task 2 started")
await asyncio.sleep(2)
print(f"[{datetime.now()}] Task 2 completed")
return "Task 2 result"
def all_tasks_completed(loop, future):
"""所有任务完成后停止事件循环"""
print(f"All tasks completed. Results: {future.result()}")
loop.stop()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# 创建任务组
tasks = asyncio.gather(task1(), task2())
# 设置任务完成回调
tasks.add_done_callback(functools.partial(all_tasks_completed, loop))
try:
print("Starting event loop...")
loop.run_forever()
finally:
loop.close()
print("Event loop closed")
1.3 事件循环的高级配置
事件循环可以通过多种方式进行配置,以优化性能或适应特定需求:
# 设置事件循环策略(Windows平台)
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# 获取当前事件循环
loop = asyncio.get_event_loop()
# 设置默认 executor
from concurrent.futures import ThreadPoolExecutor
loop.set_default_executor(ThreadPoolExecutor(max_workers=4))
# 设置调试模式
loop.set_debug(True)
# 设置日志级别
logging.basicConfig(level=logging.DEBUG)
二、普通函数的事件循环调度
asyncio允许将普通函数(非协程)作为回调函数调度到事件循环中执行,提供了灵活的任务管理方式。
2.1 立即执行:call_soon()
call_soon()方法将普通函数安排为尽快执行,但不会立即执行,而是放入事件循环的任务队列:
import asyncio
def callback_func(name, delay):
print(f"Callback {name} executed after {delay} seconds")
async def main_coroutine():
print("Main coroutine started")
await asyncio.sleep(1)
print("Main coroutine resumed")
await asyncio.sleep(1)
print("Main coroutine completed")
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# 安排回调函数
loop.call_soon(callback_func, "A", 0) # 立即执行
# 添加协程任务
loop.create_task(main_coroutine())
# 再次安排回调函数
loop.call_soon(callback_func, "B", 0) # 立即执行
loop.run_until_complete(asyncio.sleep(3))
loop.close()
执行顺序:call_soon添加的回调会按添加顺序执行,且在当前协程挂起时执行。
2.2 延迟执行:call_later()
call_later(delay, callback, *args)安排在指定延迟(秒)后执行回调函数:
import asyncio
import time
def callback(name):
print(f"[{time.ctime()}] Callback {name} executed")
async def main():
print(f"[{time.ctime()}] Main coroutine started")
loop = asyncio.get_event_loop()
# 安排延迟执行
loop.call_later(1, callback, "A") # 1秒后执行
loop.call_later(2, callback, "B") # 2秒后执行
loop.call_later(1, callback, "C") # 1秒后执行
await asyncio.sleep(3)
print(f"[{time.ctime()}] Main coroutine completed")
if __name__ == "__main__":
asyncio.run(main())
执行顺序:延迟时间短的先执行,延迟相同则按添加顺序执行。
2.3 指定时间执行:call_at()
call_at(when, callback, *args)安排在指定时间(事件循环内部时间)执行回调函数:
import asyncio
import time
def callback(name):
print(f"[{time.ctime()}] Callback {name} executed")
async def main():
loop = asyncio.get_event_loop()
now = loop.time() # 获取事件循环内部时间
print(f"[{time.ctime()}] Main coroutine started")
# 安排在指定时间执行
loop.call_at(now + 1, callback, "A") # 1秒后执行
loop.call_at(now + 2, callback, "B") # 2秒后执行
loop.call_at(now + 1.5, callback, "C") # 1.5秒后执行
await asyncio.sleep(3)
print(f"[{time.ctime()}] Main coroutine completed")
if __name__ == "__main__":
asyncio.run(main())
2.4 回调函数与协程的交互
回调函数可以与协程交互,通过Future对象传递结果:
import asyncio
def callback(future, result):
print(f"Callback received result: {result}")
future.set_result(result * 2) # 设置Future结果
async def main():
loop = asyncio.get_event_loop()
future = loop.create_future()
# 安排回调函数,传递Future对象
loop.call_soon(callback, future, 10)
print("Waiting for callback...")
result = await future # 等待Future完成
print(f"Main received result: {result}")
if __name__ == "__main__":
asyncio.run(main())
三、协程同步机制
在并发环境中,多个协程可能同时访问共享资源,需要同步机制确保数据一致性。asyncio提供了多种同步原语。
3.1 协程锁(Lock)
asyncio.Lock用于实现协程间的互斥访问,确保同一时间只有一个协程执行临界区代码:
import asyncio
import random
async def worker(name, lock, shared_resource):
async with lock: # 自动获取和释放锁
print(f"Worker {name} acquired lock")
# 访问共享资源
current_value = shared_resource["count"]
await asyncio.sleep(random.uniform(0.1, 0.5)) # 模拟处理时间
shared_resource["count"] = current_value + 1
print(f"Worker {name} released lock. New count: {shared_resource['count']}")
async def main():
shared_resource = {"count": 0}
lock = asyncio.Lock()
# 创建多个工作协程
workers = [worker(f"Worker-{i}", lock, shared_resource) for i in range(5)]
# 并发执行所有工作协程
await asyncio.gather(*workers)
print(f"Final count: {shared_resource['count']}")
if __name__ == "__main__":
asyncio.run(main())
工作原理:async with lock语句会在进入时调用lock.acquire(),退出时调用lock.release(),确保锁的正确释放。
3.2 信号量(Semaphore)
asyncio.Semaphore限制同时访问资源的协程数量:
import asyncio
import aiohttp
async def fetch_url(session, url, semaphore):
async with semaphore: # 限制并发数量
async with session.get(url) as response:
print(f"Fetch {url} status: {response.status}")
return await response.text()
async def main():
urls = [
"https://www.example.com",
"https://www.python.org",
"https://www.github.com",
"https://www.stackoverflow.com",
"https://www.baidu.com",
"https://www.google.com"
]
# 限制最多2个并发请求
semaphore = asyncio.Semaphore(2)
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url, semaphore) for url in urls]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
3.3 事件(Event)
asyncio.Event用于通知多个协程某个事件已发生:
import asyncio
async def waiter(event, name):
print(f"Waiter {name} waiting for event...")
await event.wait() # 等待事件被设置
print(f"Waiter {name} received event!")
async def setter(event):
await asyncio.sleep(2)
print("Setter setting event")
event.set() # 设置事件
async def main():
event = asyncio.Event()
# 创建等待者协程
waiters = [waiter(event, i) for i in range(3)]
# 创建设置者协程
setter_task = asyncio.create_task(setter(event))
# 并发运行所有协程
await asyncio.gather(*waiters, setter_task)
# 重置事件(可选)
event.clear()
if __name__ == "__main__":
asyncio.run(main())
3.4 条件(Condition)
asyncio.Condition结合了锁和事件的功能,允许协程在特定条件满足时被唤醒:
import asyncio
async def consumer(condition, queue, name):
async with condition:
while True:
if not queue:
print(f"Consumer {name} waiting for items...")
await condition.wait() # 等待条件通知
item = queue.pop()
print(f"Consumer {name} consumed item: {item}")
await asyncio.sleep(0.5)
async def producer(condition, queue):
for i in range(5):
await asyncio.sleep(1)
item = f"Item-{i}"
queue.append(item)
print(f"Producer added item: {item}")
async with condition:
condition.notify_all() # 通知所有等待的消费者
# 通知消费者结束
async with condition:
queue.append(None) # 结束标志
condition.notify_all()
async def main():
condition = asyncio.Condition()
queue = []
# 创建消费者
consumers = [consumer(condition, queue, i) for i in range(2)]
# 创建生产者
producer_task = asyncio.create_task(producer(condition, queue))
# 运行消费者和生产者
await asyncio.gather(*consumers, producer_task)
if __name__ == "__main__":
asyncio.run(main())
四、实战案例:异步任务调度系统
下面实现一个简单的异步任务调度系统,支持定时任务、周期性任务和一次性任务:
import asyncio
from datetime import datetime, timedelta
from typing import Callable, Any
class AsyncScheduler:
def __init__(self):
self.loop = asyncio.get_event_loop()
self.tasks = []
self.running = False
def _schedule(self, func: Callable, args: tuple, when: float):
"""安排任务在指定时间执行"""
def wrapper():
try:
result = func(*args)
if asyncio.iscoroutine(result):
self.loop.create_task(result)
except Exception as e:
print(f"Task error: {e}")
handle = self.loop.call_at(when, wrapper)
return handle
def call_later(self, delay: float, func: Callable, *args: Any):
"""延迟执行任务"""
when = self.loop.time() + delay
return self._schedule(func, args, when)
def call_at(self, when: datetime, func: Callable, *args: Any):
"""在指定时间执行任务"""
delay = (when - datetime.now()).total_seconds()
return self.call_later(max(0, delay), func, *args)
def call_periodic(self, interval: float, func: Callable, *args: Any):
"""周期性执行任务"""
def periodic_wrapper():
try:
result = func(*args)
if asyncio.iscoroutine(result):
self.loop.create_task(result)
except Exception as e:
print(f"Periodic task error: {e}")
finally:
# 安排下一次执行
self.call_later(interval, periodic_wrapper)
# 立即执行第一次
return self.call_later(0, periodic_wrapper)
async def start(self):
"""启动调度器"""
self.running = True
print("Scheduler started. Press Ctrl+C to stop.")
try:
await asyncio.Event().wait() # 无限等待
except KeyboardInterrupt:
print("Scheduler stopping...")
finally:
self.running = False
# 使用示例
async def sample_task(name):
print(f"[{datetime.now()}] Sample task {name} executed")
await asyncio.sleep(0.1)
def sync_task(name):
print(f"[{datetime.now()}] Sync task {name} executed")
if __name__ == "__main__":
scheduler = AsyncScheduler()
# 安排延迟任务
scheduler.call_later(1, sync_task, "delayed")
# 安排定时任务
future_time = datetime.now() + timedelta(seconds=2)
scheduler.call_at(future_time, sample_task, "scheduled")
# 安排周期性任务
scheduler.call_periodic(3, sample_task, "periodic")
# 启动调度器
asyncio.run(scheduler.start())
五、性能优化与最佳实践
5.1 避免阻塞操作
事件循环在单线程中运行,任何阻塞操作都会阻塞整个事件循环。对于CPU密集型任务或阻塞IO,应使用线程池或进程池:
from concurrent.futures import ThreadPoolExecutor
async def blocking_operation():
loop = asyncio.get_event_loop()
# 在线程池中运行阻塞函数
result = await loop.run_in_executor(
None, # 使用默认线程池
blocking_function, # 阻塞函数
arg1, arg2 # 函数参数
)
return result
# 自定义线程池
executor = ThreadPoolExecutor(max_workers=4)
loop.set_default_executor(executor)
5.2 任务取消与异常处理
合理处理任务取消和异常是编写健壮异步程序的关键:
async def cancellable_task():
try:
print("Task started")
for i in range(5):
await asyncio.sleep(1)
print(f"Task working... {i}")
return "Task completed"
except asyncio.CancelledError:
print("Task was cancelled")
raise # 重新抛出以通知任务已取消
finally:
print("Task cleanup")
async def main():
task = asyncio.create_task(cancellable_task())
await asyncio.sleep(2)
# 取消任务
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Main caught cancelled error")
asyncio.run(main())
5.3 限制并发数量
使用信号量限制并发数量,避免资源耗尽:
async def bounded_concurrency(tasks, limit):
semaphore = asyncio.Semaphore(limit)
async def sem_task(task):
async with semaphore:
return await task
return await asyncio.gather(*[sem_task(t) for t in tasks])
# 使用示例
tasks = [fetch_url(url) for url in many_urls]
results = await bounded_concurrency(tasks, 10) # 限制10个并发
5.4 调试异步程序
启用调试模式和日志有助于诊断问题:
# 启用调试模式
asyncio.run(main(), debug=True)
# 或手动配置
loop = asyncio.get_event_loop()
loop.set_debug(True)
# 配置详细日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s %(levelname)s %(name)s: %(message)s'
)
六、asyncio与多线程/多进程的对比
| 特性 | asyncio | 多线程 | 多进程 |
|---|---|---|---|
| 并发模型 | 单线程异步IO | 多线程并行 | 多进程并行 |
| CPU密集型任务 | 不适合 | 受GIL限制 | 适合 |
| IO密集型任务 | 非常适合 | 适合 | 适合但开销大 |
| 内存占用 | 低 | 中 | 高 |
| 切换开销 | 极低 | 中 | 高 |
| 共享状态 | 简单(单线程) | 需要锁机制 | 需要IPC机制 |
| 适用场景 | 网络爬虫、API服务、高并发IO | 中等IO并发、GUI应用 | CPU密集型计算、多核心利用 |
七、总结
本文深入探讨了Python asyncio库的高级特性,包括事件循环的精细控制、普通函数的调度执行、协程同步机制以及实战应用案例。通过掌握这些高级技巧,开发者可以构建高效、健壮的异步应用程序,特别适合处理高并发IO密集型任务。
关键要点:
- 事件循环是asyncio的核心,负责协程和回调的调度执行
- 任务调度可以通过
call_soon()、call_later()和call_at()实现普通函数的灵活执行 - 同步机制(Lock、Semaphore、Event、Condition)确保协程间安全共享资源
- 性能优化需避免阻塞操作,合理限制并发数量
- 异常处理和任务取消是编写健壮异步程序的关键
asyncio为Python异步编程提供了强大而灵活的框架,随着Python版本的不断更新,其功能也在持续增强。建议开发者深入学习官方文档,并结合实际项目实践,充分发挥异步编程的优势。
更多推荐
所有评论(0)