Rust 对协程的思考
最近和同事聊起来,觉得 lua 缺乏编译型语言的类型校验功能,还有变量拼写检查之类的,导致线上总是有低级错误出现。比如最近有一个是变量名拼写少了一个字母,导致某功能没开启;还有一个是变量传参时,之前测试多加了一个参数,测试完成后忘记删了,导致参数顺序不对。之前看过有个 TypeLua,没想到现在已经不怎么维护了,去搞了 Titan-lang,然而 Titan 也是 4 个月没动静了。。。
目前也没想到什么好的解决办法,于是顺带看看 Rust-lang,开阔一下思路。我最佩服 rust 这帮人的,是他们有点壮士断腕的勇气,已经写好的 runtime 和协程,认为和 rust 的定位不妥,就直接砍掉了。 相关的 PR 在这里 ,rfcs 在这里 。rfcs 里面关于协程、线程模型的讨论相当到位,我简要翻译一下,权当记录。以下是译文(节译):
背景:线程 / 任务模型和 I/O
很多语言 / 库会提供任务,任务一般有别于操作系统的原始线程。任务的特性可以按以下几个维度区分:
- 1:1 vs M:N 最根本的问题是一个任务是不是总是对应到一个 OS 级别的线程(1:1 模型),或者是通过用户空间调度器,将任务映射到 worker 线程上(M:N 模型)。有一些内核,比如 Windows,支持用户空间调度的 1:1 模型,结合了这两者的优势。
-
- 分段栈(segmented stacks)允许栈随着时间增长,意味着任务可以有自己的栈,而且依然保持轻量。但是,分段栈有 [ 明显的性能问题和复杂度开销 ](https://mail.mozilla.org/pipermail/rust-dev/2013-November/006314.html)。
- 在没有自己的栈的情况下,任务要么无法在工作线程间迁移(例如 Java 框架里的 [fork/join](http://gee.cs.oswego.edu/dl/papers/fj.pdf)),或者只能用 CPS(continuation-passing style)实现,即每个阻塞操作都用一个闭包保存自己的工作状态。(CPS 一般将用到的栈保存在闭包里)好处是这些任务特别轻量,基本只是闭包的开销。
Rust 的现状
Rust 从绿色线程模型(即协程模型)转向了原始线程模型:
- 在 Rust 的绿色线程模型里,任务是按 M:N 进行调度,有自己的栈。最初,Rust 使用了分段栈,后面改成了预分配的栈,这样 Rust 的绿色线程就不是轻量级的了。对阻塞的操作下文再叙
- 在 Rust 的原始线程模型里,任务是 1:1 的和 OS 线程匹配的。
(节略)
问题
强制的共同演进:绿色线程模型和原始线程模型必须提供相同的 I/O 接口,但是有部分接口只会在其中一种模型里有效。比如,轻量级
开销:目前的 Rust 模型允许运行时将绿色线程模型和原始线程模型混合使用。但是实现上有如下缺点:
- 二进制大小。任意二进制文件里都包含了整个 I/O 系统的实现,因为他是 libstd 标准库的一部分。
- 任务局部存储。目前的任务局部存储是可以无缝在原始线程和绿色线程间切换的。但是性能会有影响,即使可以改进,也比直接采用原始的线程局部存储要复杂得多。
- 动态分配和调度。当前的设计下,所有 I/O 操作都需要动态调度,大部分的内存分配操作也是。但是,绝大部分情况下,
问题重重的 I/O 交互:
嵌入式的 Rust:
维护困难:
Tag:
python