协程(Coroutine)作为Python中处理高并发IO密集型任务的核心技术,其本质是一种用户态的轻量级线程。与传统的多线程和多进程相比,协程最大的特点是在单线程内实现任务切换,避免了操作系统级别的上下文切换开销。
协程通过yield/send机制实现执行流的挂起和恢复。当协程遇到IO操作时,会自动挂起当前任务,将控制权交给事件循环(Event Loop),由事件循环调度其他就绪的协程继续执行。这种机制使得单个线程可以"同时"处理成百上千个网络连接。
关键点:协程的切换完全在用户空间完成,不涉及内核态切换,这是其高性能的根本原因
在Python 3.7+中,一个最简单的协程示例展示了其基本形态:
python复制async def simple_coroutine():
print("协程开始")
await asyncio.sleep(1) # 模拟IO操作
print("协程结束")
| 特性 | 协程 | 线程 |
|---|---|---|
| 切换开销 | 用户态切换(约100ns) | 内核态切换(约1-5μs) |
| 内存占用 | 通常2-5KB/协程 | 通常8MB/线程(64位系统) |
| 并发能力 | 单线程可支持数万协程 | 通常数百线程就会性能下降 |
| 资源共享 | 天然共享无需锁 | 需要同步机制 |
| 适用场景 | IO密集型 | CPU密集型 |
实测数据表明,在处理10,000个并发HTTP请求时:
Python的协程实现经历了几个重要阶段:
生成器阶段(Python 2.2+)
装饰器阶段(Python 3.4)
原生协程阶段(Python 3.5+)
成熟阶段(Python 3.7+)
Gevent是基于libev和greenlet实现的高性能协程库,其核心优势在于通过monkey.patch_all()自动将标准库的阻塞IO替换为非阻塞版本,实现"伪同步,真异步"的编程体验。
Gevent的魔法主要来自monkey.patch_all(),它会替换以下模块的阻塞实现:
典型补丁代码:
python复制from gevent import monkey
monkey.patch_all() # 必须在导入其他标准库之前调用
import time # 此时time.sleep已被替换为非阻塞版本
实际项目中,我们通常需要更精细的控制:
python复制from gevent.pool import Pool
def intensive_task(url):
# 模拟耗时IO操作
gevent.sleep(0.5)
return f"Processed {url}"
# 创建限制并发数的协程池
pool = Pool(100) # 最大100个并发
urls = [f"http://example.com/page{i}" for i in range(1000)]
results = pool.map(intensive_task, urls)
经验分享:在生产环境中,建议将Pool大小设置为目标服务的QPS乘以平均响应时间。例如目标QPS为200,平均响应时间为0.3秒,那么Pool大小设为60左右最合适。
Python 3.5引入的async/await语法使得协程编程更加直观。asyncio作为标准库,提供了完整的事件循环实现。
code复制asyncio事件循环
├── 协程任务(Task)
├── 未来对象(Future)
├── 传输层(Transport)
├── 协议层(Protocol)
└── 策略层(Policy)
一个完整的HTTP API服务实现:
python复制import asyncio
from aiohttp import web
async def handle(request):
name = request.match_info.get('name', "Anonymous")
# 模拟数据库查询
await asyncio.sleep(0.1)
return web.Response(text=f"Hello, {name}")
app = web.Application()
app.add_routes([
web.get('/', handle),
web.get('/{name}', handle)
])
async def background_task():
"""后台定时任务"""
while True:
print("Running background check...")
await asyncio.sleep(60)
async def start_app():
# 启动后台任务
asyncio.create_task(background_task())
# 启动Web服务
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, '0.0.0.0', 8080)
await site.start()
print("Server started at http://0.0.0.0:8080")
asyncio.run(start_app())
现代爬虫需要处理大量并发请求,协程是理想选择。以下是专业爬虫的架构设计:
code复制协程爬虫架构
├── 请求调度器(Scheduler)
├── 下载器(Downloader,协程池实现)
├── 解析器(Parser)
├── 去重过滤器(Bloom Filter)
└── 存储管道(Item Pipeline)
实战代码示例:
python复制async def crawl_page(session, url, semaphore):
async with semaphore: # 控制并发数
try:
async with session.get(url, timeout=10) as response:
if response.status == 200:
html = await response.text()
# 使用lxml解析HTML
return parse_html(html)
except Exception as e:
logger.error(f"Error crawling {url}: {str(e)}")
async def batch_crawl(urls, concurrency=100):
connector = aiohttp.TCPConnector(limit=0) # 不限制连接数
timeout = aiohttp.ClientTimeout(total=30)
semaphore = asyncio.Semaphore(concurrency)
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
tasks = [crawl_page(session, url, semaphore) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
性能优化点:
- 使用连接池管理HTTP连接
- 实现请求去重机制
- 添加适当的延迟避免被封禁
- 实现自动重试机制
在微服务架构中,服务间通信的IO等待时间占比很高。协程可以显著提升吞吐量:
python复制async def call_service(endpoint, payload):
for attempt in range(3): # 重试机制
try:
async with aiohttp.ClientSession() as session:
async with session.post(
endpoint,
json=payload,
timeout=2.0
) as response:
if response.status == 200:
return await response.json()
await asyncio.sleep(0.5 * attempt) # 指数退避
except Exception as e:
logger.warning(f"Attempt {attempt+1} failed: {str(e)}")
raise ServiceError("Max retries exceeded")
常见性能问题及解决方案:
CPU计算阻塞事件循环
python复制await asyncio.to_thread(cpu_intensive_task, data)
DNS查询阻塞
python复制async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(
use_dns_cache=True,
ttl_dns_cache=300
)) as session:
pass
连接泄漏
python复制try:
conn = await asyncpg.connect()
# 使用连接
finally:
await conn.close()
事件循环监控
python复制loop = asyncio.get_event_loop()
loop.set_debug(True) # 启用调试模式
协程执行追踪
python复制import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('asyncio')
性能分析工具
python复制import cProfile
def profile_coroutine(coro):
def wrapper():
return asyncio.run(coro())
cProfile.runctx('wrapper()', globals(), locals())
命名约定
错误处理原则
资源管理
生产者-消费者模式
python复制async def producer(queue):
while True:
item = await get_item()
await queue.put(item)
async def consumer(queue):
while True:
item = await queue.get()
await process_item(item)
queue.task_done()
发布-订阅模式
python复制async def publisher(channel):
while True:
message = await generate_message()
await channel.publish(message)
async def subscriber(channel):
async for message in channel:
await handle_message(message)
工作池模式
python复制async def worker(input_q, output_q):
while True:
task = await input_q.get()
result = await process_task(task)
await output_q.put(result)
input_q.task_done()
在实际项目中使用协程时,我发现最容易被忽视的是资源清理问题。很多开发者只关注协程的启动而忘记妥善处理关闭逻辑,这会导致连接泄漏等问题。一个健壮的协程应用应该实现优雅关闭机制,确保所有资源都被正确释放。