从错误处理到结构化并发
并发
编程范式
错误处理
结构化并发
Python
背景问题
单线程程序的错误处理范式:异常沿调用栈向上传播,直到找到处理者。
并发程序的问题:没有单一的调用栈,错误如何处理?
两种常见方案
- 方案A:打印错误,终止线程,继续运行 (Python, Java)
→ 程序继续运行但进入未测试状态,可能死锁 - 方案B:打印错误,立即终止整个程序 (Go, Rust, C++)
→ 重量级方案,可能终止关键守护进程
asyncio的"第三种方案"
- Task对象可等待,异常重新传递
- 不给程序员强加opinion,让程序员自己决定
- 缺点:如果没人等待Task,异常被吞掉
TaskLauncher模式
确保每个任务都有父任务等待:
- 创建任务时同时创建等待上下文
- 父任务负责等待子任务的异常
- 异常沿任务层级向上传播
两大挑战
1. 死锁问题
父任务等待子任务完成,但子任务失败导致永远等待。例如:4个任务中1个失败,父任务等待所有完成事件。
2. 资源泄漏
失败操作涉及多个执行任务,需要确保所有派生的任务都能清理状态。
核心洞察
"We've gone a long way towards converting the concurrent exception-handling problem into the familiar single-threaded version"
通过TaskLauncher模式,我们可以将并发异常处理问题转化为熟悉的单线程版本。
相关讨论
- Lisp的"restarts"机制
- Python的asyncio框架
- 结构化并发的理念
- RAII和context manager