4 个版本 (重大更新)
0.4.0 | 2023 年 10 月 10 日 |
---|---|
0.3.0 | 2023 年 10 月 10 日 |
0.2.0 | 2023 年 10 月 9 日 |
0.1.0 | 2023 年 10 月 8 日 |
#728 在 Rust 模式
每月 21 次下载
8KB
52 行
lucchetto
轻松调用 Rust 函数而无需持有 GVL 锁
lucchetto = 意大利语中的“锁”
如何安装
将以下内容添加到您的 Cargo.toml
[dependencies]
lucchetto = "0.2.0"
或
cargo add lucchetto
为什么?
假设您已编写一个 Rust 函数,该函数由 Ruby 调用,使用 magnus 和 rb-sys。
例如,让我们以这个简单的函数(来自我们的简单示例)为例
use magnus::{define_global_function, function};
fn slow_func(input: String) -> String {
std::thread::sleep(std::time::Duration::from_secs(2));
input.len().to_string()
}
#[magnus::init]
fn init() {
define_global_function("slow_func", function!(slow_func, 1));
}
这允许您编写如下 Ruby 脚本
require_relative "./lib/simple"
t = Thread.new do
puts slow_func("hello")
end
1..10.times do
sleep 0.1
puts "main thread"
end
t.join
但您会发现,因为您的 Rust 函数执行时间很长,所有 Ruby 线程都会阻塞,直到 Rust 函数返回。这是上述脚本的输出
main thread
5
main thread
main thread
main thread
main thread
main thread
main thread
main thread
main thread
main thread
这表明主线程在 Rust 函数返回(在不同的线程中运行)之前被阻塞。这是因为 Rust 线程持有 GVL 锁,其他 Ruby 线程无法运行。
我们能解决这个问题吗?
是的!我们可以使用 lucchetto 的简单属性宏来告诉 Ruby VM 我们不会使用 GVL 锁,并且可以在我们运行 Rust 函数时运行其他 Ruby 线程。
use lucchetto::without_gvl;
use magnus::{define_global_function, function};
#[without_gvl]
fn slow_func(input: String) -> String {
std::thread::sleep(std::time::Duration::from_secs(2));
input.len().to_string()
}
#[magnus::init]
fn init() {
define_global_function("slow_func", function!(slow_func, 1));
}
如果我们再次运行之前的 Ruby 脚本,我们会看到主线程不再被阻塞
main thread
main thread
main thread
main thread
main thread
main thread
main thread
main thread
main thread
main thread
5
如您所见,主线程不再被阻塞,Rust 函数仍在后台运行。
安全顾虑
为了防止运行可能不安全的函数,因为它可以访问 Ruby 对象,我们引入了一个名为 GvlSafe
的 trait。如果函数想要使用 #[without_gvl]
属性宏,则只能接收和返回实现此 trait 的类型。
它是一个空 trait,因此如果认为它们是安全的,可以轻松地在自己的类型上实现。示例
use lucchetto::GvlSafe;
struct MyStruct;
impl GvlSafe for MyStruct {}
您可以将 GvlSafe
视为 GVL 锁的 Send
和 Sync
。
这是好代码吗?
老实说?我不知道。它可能包含内存错误(我必须做很多指针操作才能使其工作),并且可能是不安全的。我没有花太多时间在这个上面,所以我不确定这是否是做这件事的最佳方式。但它看起来工作得怎么样?是的。
依赖项
~0.4–3MB
~54K SLoC