Skip to content

Latest commit

 

History

History
40 lines (26 loc) · 3.96 KB

File metadata and controls

40 lines (26 loc) · 3.96 KB

Async Rust can be a pleasure to work with (without Send + Sync + 'static)

TL;DR

在Rust异步编程中,通过结构化并发和线程每核心模型可规避对Send+Sync+'static依赖。结构化并发自动管理子任务生命周期,简化资源清理与错误处理;绑定单线程的运行时(如Glommio)无需任务实现Send,降低代码复杂度。实验表明,轻量任务场景下该模式性能更优,但负载不均时工作窃取模式(如Tokio)更适用。现有框架多强制Send+'static要求,需探索新方案实现开发效率与性能的平衡。

Summary

该文章讨论了在Rust中使用异步编程时如何避免依赖Send + Sync + 'static界限,并通过结构化并发和线程每核心的运行时优化开发者体验和性能。

  1. 关于'static界限的限制
    tokio::spawn等传统多线程运行时需要'static界限,因为未来(future)可能在后台无限期运行,导致无法通过编译器静态分析确定资源回收时机。为此,开发者需将对象包装在Arc<Mutex<_>>等引用计数类型中,增加复杂性。

  2. 结构化并发的解决方案
    结构化并发要求每个未来在作用域内执行,作用域结束前必须完成所有子任务。这意味着:

    • 资源清理由作用域自动管理,无需手动操作引用计数。
    • 错误自动向上传播,无需显式处理。
    • 函数作为“黑盒”抽象,调用完成后所有子任务已终止。 动态结构化并发(如morofutures-concurrency)允许未知数量的异步任务,通过作用域管理任务生命周期,无需'static。静态结构化并发(如futures::join)适合已知任务数量的场景,避免堆分配,性能更优。
  3. 关于SendSync的限制
    SendSync的约束源于多线程运行时可能在不同线程间移动任务。而线程每核心(thread-per-core)运行时如glommiomonoio将任务绑定到单线程,只需确保共享资源是Send,无需任务自身为Send。这简化了代码(如避免Mutex和频繁Arc::clone)。

  4. 线程每核心与工作窃取模式的对比
    工作窃取模式(如Tokio)在任务不均衡时通过线程间迁移提升效率,但需应对跨线程的复杂性。线程每核心模型采用“单线程负责制”,在均匀任务负载下表现更优,例如单线程单任务时Glommio的吞吐量与Tokio接近,而多线程时Glommio的尾部延迟更低。实验显示,对于较轻量的HTTP请求,Glommio的吞吐量和延迟优于或接近Tokio。

  5. 代码示例对比
    在传统模式中,需大量使用Arcasync move来确保对象跨线程可移动。使用结构化并发和线程每核心运行时后,对象可作为普通变量直接传递,避免了复杂的手动克隆和垃圾回收操作,代码简洁度提升。

  6. 框架现状与建议
    大多数流行的Rust Web框架(如Axum、Poem)要求Handler返回的未来必须为Send + 'static,限制了灵活性。作者建议社区开发支持非Send Future的框架,例如通过设计多线程时避免任务迁移的中间件,或选择如Xitca-Web等更轻量的新框架。这种设计减少代码复杂性,同时可能在特定场景下提升性能。

  7. 性能与设计权衡
    线程每核心模型在资源管理和错误传播上更具优势,但其适用场景需任务负载均匀。若任务工作量差异显著,工作窃取模式可能更有效。需根据具体需求权衡,例如Web服务可通过外部负载均衡(而非内部线程级平衡)应对任务差异。

综上,结合结构化并发和线程每核心运行时,可以大幅简化异步代码的开发,同时在许多场景下实现高性能。目前生态虽未全面支持,但已有诸多工具和框架正在探索这一方向。