4 个版本
使用旧的 Rust 2015
0.2.0 | 2018 年 2 月 22 日 |
---|---|
0.1.2 | 2018 年 2 月 20 日 |
0.1.1 | 2018 年 2 月 19 日 |
0.1.0 | 2018 年 2 月 19 日 |
#877 在 并发
55KB
1.5K SLoC
rust-coroutines
'原生'的 Rust 栈式协程库。
用 std::thread::spawn
替换 coroutines::spawn
就行。一切都会正常工作!
特性
- 大多数
libstd
的阻塞函数都被修复并自动变为异步 - 高效的异步 I/O 和任务调度
- 自愿抢占和工作窃取
它是如何工作的?
rust-coroutines 包含一个独立的运行时库,它提供协程调度并修补运行时环境,通过挂钩 libc 中的阻塞系统调用。
构建和使用
运行时库包含大量特定平台的代码,用 Rust 编写它不会带来很多好处。因此,我决定用 C 语言编写它。在使用库之前,您需要首先构建(并可选地'安装')core_impl/unblock_hook
(需要现代 C 编译器)
cd core_impl
make unblock_hook
sudo cp libunblock_hook.so /usr/lib/ # Optional (see below)
如果您无法或不想将 libunblock_hook
作为系统范围内的库进行安装,您可以在 cargo 命令中指定 RUSTFLAGS="-L/path/to/core_impl/"
,并在运行构建的二进制文件时指定 LD_LIBRARY_PATH=/path/to/core_impl/
设置运行时库后,您将能够在应用程序中使用 coroutines
crate。
例如
sleep.rs
extern crate coroutines;
fn sleep_thread() {
println!("sleep_thread enter");
std::thread::sleep(std::time::Duration::from_millis(500));
println!("sleep_thread exit");
}
fn main() {
for _ in 0..5 {
coroutines::spawn(sleep_thread);
}
std::thread::sleep(std::time::Duration::from_secs(1));
println!("main exit");
}
tcp_listener.rs
extern crate coroutines;
use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
fn start() {
let listener = TcpListener::bind("127.0.0.1:9781").unwrap();
for stream in listener.incoming() {
if let Ok(stream) = stream {
coroutines::spawn(move || stream.write("Hello world\n".as_bytes()));
} else {
println!("{:?}", stream.unwrap_err());
}
}
}
fn main() {
coroutines::spawn(start).join().unwrap();
}
更多示例可以在 examples/
中找到。
需要注意的是,与 阻塞 libstd
API 调用相关的所有数据结构都应该在协程中构建,以通知运行时库应用必要的补丁。 通常,这可以通过从 main
中创建一个协程并在那里完成所有操作来实现。如果不这样做,可能会导致意外的阻塞行为(例如,如果 TcpListener
对象在协程外部构建且没有设置 nonblocking
标志位,则 TcpListener::accept
将阻塞而不是产生)。
性能
rust-coroutines 的性能与其他大多数 Rust 中的 (堆栈/无堆栈) 协程库相似,包括 may 和 futures(与 tokio 一起)。
test async ... bench: 31,028,198 ns/iter (+/- 2,738,679)
test async_cpupool ... bench: 29,813,426 ns/iter (+/- 14,394,817)
test async_cpupool_cpus ... bench: 31,497,284 ns/iter (+/- 7,074,686)
test async_cpupool_many ... bench: 30,785,178 ns/iter (+/- 7,982,811)
test async_cpus ... bench: 25,660,063 ns/iter (+/- 204,811,182)
test async_many ... bench: 24,347,067 ns/iter (+/- 1,980,457)
test corona ... bench: 38,565,408 ns/iter (+/- 1,717,367)
test corona_blocking_wrapper ... bench: 38,856,394 ns/iter (+/- 2,242,614)
test corona_blocking_wrapper_cpus ... bench: 29,147,673 ns/iter (+/- 89,727,410)
test corona_blocking_wrapper_many ... bench: 28,384,512 ns/iter (+/- 2,480,628)
test corona_cpus ... bench: 28,862,550 ns/iter (+/- 2,197,395)
test corona_many ... bench: 28,509,142 ns/iter (+/- 2,767,814)
test coroutines ... bench: 26,673,276 ns/iter (+/- 3,232,604)
test coroutines_cpus ... bench: 27,194,849 ns/iter (+/- 2,787,879)
test coroutines_many ... bench: 26,744,986 ns/iter (+/- 3,161,595)
test futures ... bench: 30,695,434 ns/iter (+/- 2,447,777)
test futures_cpupool ... bench: 29,626,141 ns/iter (+/- 3,090,787)
test futures_cpupool_cpus ... bench: 30,573,408 ns/iter (+/- 3,549,979)
test futures_cpupool_many ... bench: 30,276,154 ns/iter (+/- 4,121,736)
test futures_cpus ... bench: 24,814,705 ns/iter (+/- 2,107,849)
test futures_many ... bench: 24,750,719 ns/iter (+/- 1,875,699)
test may ... bench: 27,553,510 ns/iter (+/- 3,164,095)
test may_cpus ... bench: 28,315,233 ns/iter (+/- 2,892,440)
test may_many ... bench: 27,812,071 ns/iter (+/- 2,814,072)
test threads ... bench: 42,440,270 ns/iter (+/- 4,181,660)
test threads_cpus ... bench: 40,202,798 ns/iter (+/- 6,837,590)
test threads_many ... bench: 40,259,324 ns/iter (+/- 864,916,488)
工作窃取
在大多数情况下,将协程迁移到另一个操作系统线程是安全的,但如果用户代码使用了与当前线程相关的内容——或者说更一般地,如果不是 Send
,则不是。例如,线程局部存储(TLS)。因此,协程的迁移(工作窃取)默认是禁用的,需要手动配置才能启用。
已知问题
- 同步原语尚未补丁,在等待某物可用时(例如,锁和条件变量)将阻塞当前执行单元。如果运行时检测到一段时间内没有可用的执行器,它将启动一个新的执行器,但这种解决方案比一组正确实现的异步替代方案花费更多的时间和系统资源。
- 线程局部存储(TLS)尚未补丁,在协程中使用时可能会产生意外的结果(尽管使用安全的用户代码且禁用了工作窃取,也不会违反 Rust 的安全规则)。
- 目前仅支持 x86-64 Linux。计划移植到其他类 Unix 系统、i386 和 ARM 架构。
依赖关系
~160KB