从错误处理到结构化并发

⭐⭐⭐⭐ 4星 来源: Nelson Elhage Blog
并发 编程范式 错误处理 结构化并发 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