#多线程 #线程 #线程池 #web-worker #swc #并行处理

wasm-mt-swc

swc (https://github.com/swc-project/swc) 的自定义版本

4 个版本

0.1.3 2022 年 12 月 3 日
0.1.2 2022 年 6 月 17 日
0.1.1 2021 年 3 月 12 日
0.1.0 2020 年 12 月 28 日

#1189 in WebAssembly


用于 2 个库 (通过 wasm-mt-test)

MIT/Apache

7KB
85

wasm-mt

文档 | GitHub |

crates MIT licensed CI

Rust 和 WebAssembly 的多线程库。

wasm-mt 帮助您创建和执行基于 Web Worker 的线程。您可以使用 Rust 闭包简单地编程这些线程,并使用 async/await 进行编排。

示例

  • wasm-mt-pool - 基于 wasm-mt 的线程池库。 [ | 源码 ]

您可以在浏览器中运行以下所有应用程序!

  • 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效率较低,因为它不包括对标准线程原语操作的支持。

  • 基于共享内存的消息传递和互斥锁,
  • 原子指令以及根据线程提案的内存高效处理。

谢谢

入门指南

要求

Cargo.toml

wasm-mt = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_closure = "0.3"

创建线程

首先,使用new创建一个WasmMt线程构建器,并对其进行初始化

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)

依赖项

~23–33MB
~554K SLoC