在Python的并发编程领域,生成器与异步IO的组合堪称"黄金搭档"。这对组合既能发挥生成器的惰性计算特性,又能借助异步IO实现非阻塞IO操作。本文将通过十个实战场景,展示如何用最Pythonic的方式玩转高并发。
一、生成器变身协程:从yield到await的进化论
传统生成器通过yield实现生产者-消费者模式,而当yield from遇上asyncio事件循环,便催生出新一代协程。看这个爬虫片段:
async def fetch(url):
loop = asyncio.get_event_loop()
future = loop.run_in_executor(None, requests.get, url)
response = await future
return response.text
async def main():
tasks = [fetch(url) for url in urls]
return await asyncio.gather(*tasks)
这里yield from被await取代,但底层机制依然保留:事件循环接管控制权,在IO等待期间执行其他任务。关键区别在于异步生成器能直接处理IO多路复用,而无需线程切换开销。
二、流量削峰利器:背压控制的生成器管道
面对突发流量时,传统线程池容易因资源耗尽崩溃。用生成器构建带缓冲的异步管道:
async def rate_limiter(max_concurrent):
semaphore = asyncio.Semaphore(max_concurrent)
async with semaphore:
yield
async def process_batch(items):
async with rate_limiter(100): # 每秒最多处理100个
for item in items:
await asyncio.sleep(0.01) # 模拟处理延迟
yield item
通过协程挂起实现天然背压,当消费者处理速度跟不上时,生产者会自动暂停,避免内存爆炸。这种设计比手动实现队列+信号量更简洁高效。
三、异步上下文管理器:资源管理的优雅之道
处理数据库连接等需要清理的资源时,异步上下文管理器是最佳拍档:
@asynccontextmanager
async def acquire_connection():
conn = await pool.connect()
try:
yield conn
finally:
await conn.close()
async def query_data():
async with acquire_connection() as conn:
result = await conn.fetch("SELECT ...")
return process(result)
相比同步版本的with语句,异步上下文管理器能确保在IO等待期间释放资源,避免连接泄漏。注意asynccontextmanager需要Python 3.7+支持。
四、生成器表达式×异步迭代:内存友好的数据处理
处理日志文件等大数据流时,同步生成器表达式会阻塞事件循环。改用异步版本:
async def tail_file(filename):
while True:
line = await async_read_line(filename) # 自定义异步读取
if not line:
await asyncio.sleep(0.1)
continue
yield line
async def process_logs():
async for line in tail_file("app.log"):
if "ERROR" in line:
await send_alert(line)
这里用async for替代普通生成器,配合异步文件读取,既能实时处理日志,又不会阻塞其他任务执行。
五、超时控制的艺术:CancellationToken模式
在分布式系统中,超时控制至关重要。用生成器实现灵活的超时机制:
async def with_timeout(coro, timeout):
future = asyncio.ensure_future(coro)
try:
return await asyncio.wait_for(future, timeout)
except asyncio.TimeoutError:
future.cancel()
raise
async def fetch_with_retry(url, retries=3):
for _ in range(retries):
try:
return await with_timeout(fetch(url), 5)
except (TimeoutError, ConnectionError):
continue
raise MaxRetriesExceeded()
通过包装协程并设置超时,既能防止任务挂起,又能实现优雅的重试逻辑。注意要正确处理CancelledError异常。
六、并发可视化:用生成器追踪执行流
调试并发代码时,传统打印日志容易错乱。用生成器记录执行轨迹:
async def trace_coroutine(coro):
trace = []
async def wrapper():
trace.append(f"START {coro.__name__}")
result = await coro
trace.append(f"END {coro.__name__}")
return result, trace
return await wrapper()
async def main():
task1 = trace_coroutine(fetch("https://a.com"))
task2 = trace_coroutine(fetch("https://b.com"))
_, traces = await asyncio.gather(task1, task2)
print("\n".join(sorted("".join(t) for t in traces)))
通过装饰器模式收集执行轨迹,最后按时间顺序输出,能清晰看到任务切换点。
七、优先级调度:生成器权重队列
当需要处理不同优先级任务时,自定义异步调度器:
class PriorityQueue:
def __init__(self):
self._queue = []
async def put(self, item, priority):
heapq.heappush(self._queue, (priority, item))
async def get(self):
while True:
if self._queue:
return heapq.heappop(self._queue)[1]
await asyncio.sleep(0.01) # 避免忙等待
async def scheduler():
queue = PriorityQueue()
while True:
task = await queue.get()
await task()
通过优先队列管理任务,高优先级任务能立即抢占执行权。注意要用await asyncio.sleep避免阻塞事件循环。
八、熔断降级:生成器实现的自我保护
在微服务架构中,熔断器模式至关重要。用生成器实现简易熔断:
class CircuitBreaker:
def __init__(self, failure_threshold=3, reset_timeout=30):
self.failure_count = 0
self.last_failure = 0
self.threshold = failure_threshold
self.reset_timeout = reset_timeout
async def __call__(self, func):
async def wrapper(*args, **kwargs):
if self.is_open():
await asyncio.sleep(self.reset_timeout)
self.failure_count = 0
self.last_failure = 0
try:
return await func(*args, **kwargs)
except Exception:
self.failure_count += 1
self.last_failure = time.time()
if self.failure_count >= self.threshold:
self._open_circuit()
raise
return wrapper
def is_open(self):
return self.failure_count >= self.threshold and (
time.time() - self.last_failure < self.reset_timeout
)
def _open_circuit(self):
# 触发降级逻辑,如返回默认值或缓存
pass
通过装饰器模式包裹协程,当失败次数超过阈值时自动熔断,避免雪崩效应。
九、分布式锁:基于Redis的异步实现
在分布式环境中,用生成器实现轻量级锁:
async def acquire_lock(lock_name, expire=10):
key = f"lock:{lock_name}"
while True:
if await redis.set(key, "1", ex=expire, nx=True):
return key
await asyncio.sleep(0.1)
async def release_lock(key):
await redis.delete(key)
async def safe_operation():
lock_key = await acquire_lock("resource_x")
try:
await do_critical_section()
finally:
await release_lock(lock_key)
使用Redis的SETNX命令实现分布式锁,配合异步客户端实现非阻塞获取。注意要处理锁过期和异常释放的情况。
十、性能剖析:生成器驱动的火焰图
当遇到性能瓶颈时,用生成器收集追踪数据:
async def profile(coro):
start = time.perf_counter()
result = await coro
duration = time.perf_counter() - start
return result, duration
async def analyze_performance():
tasks = [profile(fetch(url)) for url in urls]
results, durations = zip(*await asyncio.gather(*tasks))
print(f"Avg duration: {sum(durations)/len(durations):.2f}s")
通过装饰器模式统计每个协程的执行时间,结合cProfile或py-spy工具生成火焰图,能直观看到热点函数。
实战心法:
- 协程不是线程,不要用threading的思维写异步代码
- 避免在协程中执行阻塞操作,必要时用loop.run_in_executor
- 合理设置超时,防止僵尸任务耗尽资源
- 善用async with管理资源,比手动清理更安全
- 日志中记录协程ID(asyncio.get_running_loop().get_debug().asyncio_coroutine_id)有助于追踪执行流
生成器与异步IO的组合,本质是用协作式调度替代抢占式调度。理解事件循环的工作原理,掌握协程的挂起与恢复时机,就能在资源占用与吞吐量之间找到最佳平衡点。这种编程范式虽需改变思维习惯,但换来的代码简洁性和执行效率,在I/O密集型场景中绝对值得投入学习成本。