5 个版本
0.1.4 | 2023 年 8 月 23 日 |
---|---|
0.1.3 | 2022 年 7 月 2 日 |
0.1.2 | 2022 年 3 月 14 日 |
0.1.1 | 2022 年 2 月 25 日 |
0.1.0 | 2022 年 2 月 25 日 |
#24 in 并发
133,446 每月下载量
在 271 个 crates (4 直接) 中使用
315KB
4.5K SLoC
corosensei
文档
变更日志
概述
本 crate 提供了一种在多个堆栈之间进行上下文切换的安全高效抽象,形式为 协程。协程是一个可以被暂停和恢复的函数,可以向调用者返回值。协程可以在其调用堆栈的任何位置挂起自己。除了从协程接收返回值外,每次恢复协程时还可以向其传递数据。
示例
use corosensei::{Coroutine, CoroutineResult};
fn main() {
println!("[main] creating coroutine");
let mut coroutine = Coroutine::new(|yielder, input| {
println!("[coroutine] coroutine started with input {}", input);
for i in 0..5 {
println!("[coroutine] yielding {}", i);
let input = yielder.suspend(i);
println!("[coroutine] got {} from parent", input)
}
println!("[coroutine] exiting coroutine");
});
let mut counter = 100;
loop {
println!("[main] resuming coroutine with argument {}", counter);
match coroutine.resume(counter) {
CoroutineResult::Yield(i) => println!("[main] got {:?} from coroutine", i),
CoroutineResult::Return(()) => break,
}
counter += 1;
}
println!("[main] exiting");
}
输出
[main] creating coroutine
[main] resuming coroutine with argument 100
[coroutine] coroutine started with input 100
[coroutine] yielding 0
[main] got 0 from coroutine
[main] resuming coroutine with argument 101
[coroutine] got 101 from parent
[coroutine] yielding 1
[main] got 1 from coroutine
[main] resuming coroutine with argument 102
[coroutine] got 102 from parent
[coroutine] yielding 2
[main] got 2 from coroutine
[main] resuming coroutine with argument 103
[coroutine] got 103 from parent
[coroutine] yielding 3
[main] got 3 from coroutine
[main] resuming coroutine with argument 104
[coroutine] got 104 from parent
[coroutine] yielding 4
[main] got 4 from coroutine
[main] resuming coroutine with argument 105
[coroutine] got 105 from parent
[coroutine] exiting coroutine
[main] exiting
支持的目标
本 crate 目前支持以下目标
ELF (Linux, BSD, 嵌入式系统等) | Darwin (macOS, iOS 等) | Windows | |
---|---|---|---|
✅ x86_64 | ✅ x86 | ✅ x86 | ✅ x86 |
❌ | ✅ x86 | ⚠️* | AArch64 |
ARM | ✅ x86 | ✅ x86 | ⚠️* |
RISC-V | ✅ x86 | ⚠️* | ⚠️* |
LoongArch64 | ✅ x86 | ⚠️* | ⚠️* |
❌ | ✅ x86 | ⚠️* | ⚠️* |
* 在 x86 Windows 上不支持链式回溯。
如果您的目标尚未支持,请随时提交问题。
功能
恐慌传播
如果协程中发生恐慌,则恐慌将通过协程堆栈展开,然后继续从最后恢复它的调用者展开。一旦发生这种情况,协程被认为是完成的,并且无法再次恢复。
use std::panic::{catch_unwind, AssertUnwindSafe};
use corosensei::Coroutine;
fn main() {
println!("[main] creating coroutine");
let mut coroutine = Coroutine::new(|yielder, ()| {
println!("[coroutine] yielding 42");
yielder.suspend(42);
println!("[coroutine] panicking");
panic!("foobar");
});
println!("[main] resuming coroutine");
let result = catch_unwind(AssertUnwindSafe(|| coroutine.resume(())));
println!(
"[main] got value {} from coroutine",
result.unwrap().as_yield().unwrap()
);
println!("[main] resuming coroutine");
let result = catch_unwind(AssertUnwindSafe(|| coroutine.resume(())));
println!(
"[main] caught panic \"{}\" from coroutine",
result.unwrap_err().downcast_ref::<&'static str>().unwrap()
);
println!("[main] exiting");
}
[main] creating coroutine
[main] resuming coroutine
[coroutine] yielding 42
[main] got value 42 from coroutine
[main] resuming coroutine
[coroutine] panicking
thread 'main' panicked at 'foobar', examples/panic.rs:13:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[main] caught panic "foobar" from coroutine
[main] exiting
链式回溯
从协程内部获取的回溯将从一个协程上次恢复的点继续到父堆栈。这对于调试协程中的代码问题非常有帮助。
注意这个示例中的回溯显示协程最后是从 sub_function
恢复的
use backtrace::Backtrace;
use corosensei::Coroutine;
#[inline(never)]
fn sub_function(coroutine: &mut Coroutine<(), (), ()>) {
coroutine.resume(());
}
fn main() {
let mut coroutine = Coroutine::new(move |_, ()| {
let trace = Backtrace::new();
println!("{:?}", trace);
});
sub_function(&mut coroutine);
}
0: backtrace::main::{{closure}}
at examples/backtrace.rs:11:21
corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::with_stack::coroutine_func::{{closure}}
at src/coroutine.rs:174:62
corosensei::unwind::catch_unwind_at_root
at src/unwind.rs:43:16
1: corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::with_stack::coroutine_func
at src/coroutine.rs:174:30
2: stack_init_trampoline
3: corosensei::arch::x86_64::switch_and_link
at src/arch/x86_64.rs:302:5
corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::resume_inner
at src/coroutine.rs:256:13
corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::resume
at src/coroutine.rs:235:19
backtrace::sub_function
at examples/backtrace.rs:6:5
4: backtrace::main
at examples/backtrace.rs:15:5
链式回溯使用平台的标准展开元数据和调试器、性能分析器以及许多其他工具兼容。
析构时的清理
如果在协程挂起时将其丢弃,则将使用与panic相同的机制安全地展开协程的堆栈,这将丢弃堆栈上的任何局部变量。这是为了保持安全的API:只要堆栈上还有活动对象,就不能释放或重用协程的堆栈。
如果不需要此清理,则可以使用不安全的force_reset
函数强制将协程标记为已完成。
陷阱处理
对于高级用例,此crate提供从协程中发生的陷阱(例如,段错误、堆栈溢出)中恢复的能力。此API是不安全的,应在信号/异常处理程序中使用:它将帮助设置信号/异常处理程序的返回,以便协程立即以给定的返回值退出,并将控制权返回给其父进程。
这对于运行具有固定堆栈限制的JIT编译代码非常有用:堆栈溢出可以由信号/异常处理程序捕获并轻松恢复。
Cargo功能
当所有Cargo功能都禁用时,此crate与#![no_std]
兼容。
此crate上可用的Cargo功能标志如下
default-stack
(默认启用)
此功能提供了一个DefaultStack
实现,该实现可用于Coroutine
类型。此堆栈使用操作系统API以保护页分配,并需要std
。
如果禁用此功能,则必须实现自己的堆栈类型,该类型实现Stack
特质。
unwind
(默认启用)
此功能添加了对以下功能的支持:
- 在协程中展开panic以返回其调用者。
- 通过
force_unwind
或当协程被丢弃时强制展开挂起的协程。
请注意,如果禁用此功能,并且当协程挂起时被丢弃(即它至少已恢复一次但尚未返回)时,程序将中止。
需要std
。
asm-unwind
此功能使用asm_unwind
夜间功能,允许panic直接通过此crate中使用的内联汇编展开,这可以提高性能,因为它不需要作为Result
跨堆栈边界传递。
意味着unwind
。
性能
Corosensei已经针对性能进行了大量优化,切换堆栈只需要几个CPU周期。
可以通过cargo bench
提供两个基准测试。
- "Coroutine switch"衡量恢复协程并使其返回其调用者所需的时间。
- "Coroutine call"衡量创建一个立即返回的新协程所需的时间,调用它然后丢弃协程。
在Linux上运行时的基准测试结果
架构 | CPU | 频率 | 基准测试 | 时间 | 周期 |
---|---|---|---|---|---|
ARM | 苹果M1 Max | 2.06GHz - 3.22GHz | Coroutine switch | 3.8665 ns | N/A |
ARM | 苹果M1 Max | 2.06GHz - 3.22GHz | Coroutine call | 6.4813 ns | N/A |
x86-64 | AMD Ryzen 9 3950X | 3.5GHz - 4.7GHz | Coroutine switch | 4.2867 ns | 14.8249 |
x86-64 | AMD Ryzen 9 3950X | 3.5GHz - 4.7GHz | Coroutine call | 5.5082 ns | 19.0069 |
ARM | ARM Cortex-A72 | 1.6GHz | Coroutine switch | 16.278 ns | N/A |
ARM | ARM Cortex-A72 | 1.6GHz | Coroutine call | 18.769 ns | N/A |
❌ | 龙芯3A5000 | 2.3GHz | Coroutine switch | 9.2831 ns | N/A |
❌ | 龙芯3A5000 | 2.3GHz | Coroutine call | 16.101 纳秒 | N/A |
版权信息
本库受到以下项目的启发:
许可证
根据以下任一许可证授权:
- Apache许可证2.0版本(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT许可证(LICENSE-MIT 或 https://opensource.org/licenses/MIT)
由您选择。
贡献
除非您明确声明,否则根据Apache-2.0许可证定义,您有意提交的任何贡献,将按照上述方式双许可,无需任何额外条款或条件。
依赖项
~0-11MB
~72K SLoC