wasm-mt
=======
[Docs](https://docs.rs/wasm-mt) |
[GitHub](https://github.com/w3reality/wasm-mt) |
[Crate](https://crates.io/crates/wasm-mt)
[![crates][crates-badge]][crates-url]
[![MIT licensed][mit-badge]][mit-url]
[![CI][actions-badge]][actions-url]
[crates-badge]: https://img.shields.io/crates/v/wasm-mt.svg
[crates-url]: https://crates.io/crates/wasm-mt
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: https://github.com/w3reality/wasm-mt/blob/master/LICENSE-MIT
[actions-badge]: https://github.com/w3reality/wasm-mt/workflows/CI/badge.svg
[actions-url]: https://github.com/w3reality/wasm-mt/actions
A multithreading library for Rust and WebAssembly.
`wasm-mt` helps you create and execute Web Worker based threads. You can program the threads simply using Rust closures and orchestrate them with `async/await`.
#### Examples
- **`wasm-mt-pool`** - Thread pool library based on `wasm-mt`. [ [crate](https://crates.io/crates/wasm-mt-pool) | [source](https://github.com/w3reality/wasm-mt/tree/master/crates/pool) ]
You can run all the following apps in browser!
- **exec** - How to use wasm_mt
. [ [live](https://w3reality.github.io/wasm-mt/examples/exec/index.html) | [source](https://github.com/w3reality/wasm-mt/tree/master/examples/exec) ]
- **fib** - Computing a Fibonacci sequence with nested threads. [ [live](https://w3reality.github.io/wasm-mt/examples/fib/index.html) | [source](https://github.com/w3reality/wasm-mt/tree/master/examples/fib) ]
- **executors** - Minimal serial/parallel executors using wasm_mt
. [ [live](https://w3reality.github.io/wasm-mt/examples/executors/index.html) | [source](https://github.com/w3reality/wasm-mt/tree/master/examples/executors) ]
- **parallel** - Julia set benchmark of serial/parallel executors. [ [live](https://w3reality.github.io/wasm-mt/examples/parallel/index.html) | [source](https://github.com/w3reality/wasm-mt/tree/master/examples/parallel) ]
- **arraybuffers** - Demo of using WasmMt::new_with_arraybuffers()
. [ [live](https://w3reality.github.io/wasm-mt/examples/arraybuffers/index.html) | [source](https://github.com/w3reality/wasm-mt/tree/master/examples/arraybuffers) ]
#### Background and implementation
The preceding seminal work entitled ["Multithreading Rust and Wasm"](https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html) by [@alexcrichton](https://github.com/alexcrichton) centers on [*Web Workers*](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), *shared memory*, and [the WebAssembly threads proposal](https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md). Shared memory is built on top of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) whose [availability across major browsers](https://caniuse.com/#feat=sharedarraybuffer) has been somewhat limited. Also, the rust-wasm thread implementation work, along with the threads proposal, seems still in progress.
On the contrary, Web Worker based multithreading in JavaScript has been [well supported for a long time](https://caniuse.com/#feat=webworkers). After experimenting, we have come up to a Rust ergonomic multithreading solution that does not require `SharedArrayBuffer`. It just works across all major browsers today and we named it `wasm-mt`.
Internally, we use the [`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) Web Worker API (through bindings provided by [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen)) to initialize spawned threads. And, importantly, we keep using `postMessage()` for dynamically sending Rust closures (serialized by [`serde_traitobject`](https://github.com/alecmocatta/serde_traitobject)) to the spawned threads. By doing so, the parent thread can `await` the results of the closures executed in the spawned thread. We have found that this approach is highly flexible for extension, too. For example, it is straightforward to augment `WasmMt::Thread` to support more customized inter-thread communication patterns.
Note, however, that `wasm-mt` has some remarkable limitations compared to the ongoing shared memory based multithreading work led by `wasm-bindgen`. `wasm-mt` is not efficient in that it does **not include** support of the standard thread primitive operations:
- shared memory based message passing and mutexes,
- atomic instructions and efficient memory handling per [the threads proposal](https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md).
#### Thanks
- [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) developers
- [@alecmocatta](https://github.com/alecmocatta) for the [serde_traitobject](https://github.com/alecmocatta/serde_traitobject) crate
- [swc-project](https://github.com/swc-project) that facilitates the [wasm-mt-test](https://github.com/w3reality/wasm-mt/tree/master/crates/test) crate
# Getting started
Requirements:
- rustc (nightly)
- [`wasm-pack build`](https://github.com/rustwasm/wasm-pack#%EF%B8%8F-commands) with the [`--target no-modules`](https://rustwasm.github.io/docs/wasm-bindgen/reference/deployment.html#without-a-bundler) option
Cargo.toml:
```toml
wasm-mt = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_closure = "0.3"
```
# Creating a thread
First, create a [`WasmMt`] thread builder with [`new`][WasmMt::new] and initialize it:
```rust
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();
```
Then, create a [`wasm_mt::Thread`][Thread] with the [`thread`][WasmMt::thread] function and initialize it:
```rust
let th = mt.thread().and_init().await.unwrap();
```
# Executing a thread
Using the [`exec!`] macro, you can execute a closure in the thread and `await` the result:
```rust
// 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));
```
You can also execute an [async closure] with `exec!`:
```rust
// 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));
```
# Executing JavaScript in a thread
Using the [`exec_js!`] macro, you can execute JavaScript within a thread:
```rust
let ans = exec_js!(th, "
const add = (a, b) => a + b;
return add(1, 2);
").await?;
assert_eq!(ans, JsValue::from(3));
```
Similarly, use [`exec_js_async!`] for running asynchronous JavaScript:
```rust
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));
```
# Making executors
By using [`wasm_mt:Thread`][Thread], you can easily create custom executors. One such example is the [`wasm-mt-pool` crate](https://crates.io/crates/wasm-mt-pool). It provides a [thread pool](https://doc.rust-lang.org/book/ch20-02-multithreaded.html#improving-throughput-with-a-thread-pool) that is based on the [work stealing] scheduling strategy.
Here, for simplicity, we show the implementation of much more straightforward executors: a serial executor and a parallel executor.
First, prepare a `Vec` containing initialized threads:
```rust
let mut v: Vec = vec![];
for i in 0..4 {
let th = mt.thread().and_init().await?;
v.push(th);
}
```
Then, here's the executors in action. Note, in the latter case, we are using [`wasm_bindgen_futures::spawn_local`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/fn.spawn_local.html) to dispatch the threads in parallel.
```rust
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);
});
}
```
Observe the starting/ending timing of each thread in the developer console:
```text
🚀 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)
```
[async closure]: https://github.com/rust-lang/rfcs/blob/master/text/2394-async_await.md#async--closures
[work stealing]: https://en.wikipedia.org/wiki/Work_stealing