4 个版本
0.1.3 | 2022 年 12 月 3 日 |
---|---|
0.1.2 | 2021 年 3 月 15 日 |
0.1.1 | 2020 年 12 月 28 日 |
0.1.0 | 2020 年 5 月 25 日 |
#812 in WebAssembly
在 wasm-mt-pool-test 中使用
65KB
848 行
wasm-mt
Rust 和 WebAssembly 的多线程库。
wasm-mt
帮助您创建和执行基于 Web Worker 的线程。您可以使用 Rust 闭包简单编程线程,并使用 async/await
进行编排。
示例
您可以在浏览器中运行以下所有应用!
- exec - 如何使用
wasm_mt
。 [ 实时 | 源代码 ] - fib - 使用嵌套线程计算斐波那契数列。 [ 实时 | 源代码 ]
- executors - 使用
wasm_mt
的最小串行/并行执行器。 [ 实时 | 源代码 ] - parallel - 串行/并行执行器的 Julia 集合基准测试。 [ 实时 | 源代码 ]
- arraybuffers - 使用
WasmMt::new_with_arraybuffers()
的演示。 [ 实时 | 源代码 ]
背景和实现
由 @alexcrichton 编写的题为“Rust 和 Wasm 的多线程”的开创性工作集中在 Web Workers、共享内存 以及 WebAssembly 线程提案 上。共享内存建立在 SharedArrayBuffer
之上,其 在主流浏览器中的可用性 相对有限。此外,rust-wasm 线程实现工作以及线程提案似乎仍在进行中。
相反,基于 Web Worker 的 JavaScript 多线程已得到了 长期的良好支持。经过实验,我们提出了一种 Rust 人体工程学多线程解决方案,它不需要 SharedArrayBuffer
。它现在可以在所有主流浏览器上运行,我们将其命名为 wasm-mt
。
内部,我们使用 postMessage()
Web Worker API(通过 wasm-bindgen
提供的绑定)来初始化生成的线程。并且,重要的是,我们继续使用 postMessage()
来动态地将 Rust 闭包(由 serde_traitobject
序列化)发送到生成的线程。通过这种方式,父线程可以 await
生成线程中执行的闭包的结果。我们发现这种方法在扩展方面也非常灵活。例如,将 WasmMt::Thread
扩展以支持更多定制的线程间通信模式很简单。
但是请注意,与 wasm-bindgen
领导的基于共享内存的多线程工作进行相比,wasm-mt
有一些显著的局限性。与基于共享内存的多线程工作相比,wasm-mt
的效率不高,因为它 不包含 标准线程原语操作的支持
- 基于共享内存的消息传递和互斥锁,
- 原子指令和每个 线程提案 的有效内存管理。
感谢
- wasm-bindgen 开发者
- @alecmocatta 为 serde_traitobject 框架
- swc-project,它简化了 wasm-mt-test 框架的构建
入门指南
需求
- rustc (nightly)
wasm-pack build
带有--target no-modules
选项
Cargo.toml
wasm-mt = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_closure = "0.3"
创建线程
首先,使用 WasmMt
构建器中的 new
创建一个线程,并初始化它
use wasm_mt::prelude::*;
let pkg_js = "./pkg/exec.js"; // path to `wasm-bindgen`'s JS binding
let mt = WasmMt::new(pkg_js).and_init().await.unwrap();
然后,使用 thread
函数创建一个 [wasm_mt::Thread
][Thread],并初始化它
let th = mt.thread().and_init().await.unwrap();
执行线程
使用 exec!
宏,你可以在线程中执行闭包并 await
结果
// fn add(a: i32, b: i32) -> i32 { a + b }
let a = 1;
let b = 2;
let ans = exec!(th, move || {
let c = add(a, b);
Ok(JsValue::from(c))
}).await?;
assert_eq!(ans, JsValue::from(3));
你还可以使用 exec!
执行一个 异步闭包
// use wasm_mt::utils::sleep;
// async fn sub(a: i32, b: i32) -> i32 {
// sleep(1000).await;
// a - b
// }
let a = 1;
let b = 2;
let ans = exec!(th, async move || {
let c = sub(a, b).await;
Ok(JsValue::from(c))
}).await?;
assert_eq!(ans, JsValue::from(-1));
在线程中执行 JavaScript
使用 exec_js!
宏,你可以在线程中执行 JavaScript
let ans = exec_js!(th, "
const add = (a, b) => a + b;
return add(1, 2);
").await?;
assert_eq!(ans, JsValue::from(3));
类似地,使用 exec_js_async!
来运行异步 JavaScript
let ans = exec_js_async!(th, "
const sub = (a, b) => new Promise(resolve => {
setTimeout(() => resolve(a - b), 1000);
});
return await sub(1, 2);
").await?;
assert_eq!(ans, JsValue::from(-1));
创建执行器
通过使用 [wasm_mt:Thread
][Thread],你可以轻松地创建自定义执行器。一个例子是 wasm-mt-pool
crate。它提供了一个基于 work stealing 调度策略的 线程池
这里,为了简单起见,我们展示了更直接的执行器的实现:串行执行器和并行执行器。
首先,准备一个包含初始化线程的 Vec<wasm_mt::Thread>
let mut v: Vec<wasm_mt::Thread> = vec![];
for i in 0..4 {
let th = mt.thread().and_init().await?;
v.push(th);
}
然后,这里是执行器的实际运行情况。注意,在后一种情况下,我们使用 wasm_bindgen_futures::spawn_local
来并行调度线程。
console_ln!("🚀 serial executor:");
for th in &v {
console_ln!("starting a thread");
let ans = exec!(th, move || Ok(JsValue::from(42))).await?;
console_ln!("ans: {:?}", ans);
}
console_ln!("🚀 parallel executor:");
for th in v {
spawn_local(async move {
console_ln!("starting a thread");
let ans = exec!(th, move || Ok(JsValue::from(42))).await.unwrap();
console_ln!("ans: {:?}", ans);
});
}
在开发者控制台中观察每个线程的启动/结束时间
🚀 serial executor:
starting a thread
ans: JsValue(42)
starting a thread
ans: JsValue(42)
starting a thread
ans: JsValue(42)
starting a thread
ans: JsValue(42)
🚀 parallel executor:
(4) starting a thread
(4) ans: JsValue(42)
lib.rs
:
使用 wasm-mt
测试 crate 的实用工具。
依赖关系
~47MB
~874K SLoC