并行光线追踪

查看完整源代码在线查看已编译的示例

这是一个使用 WebAssembly、Rust 和 wasm-bindgen 进行多线程的示例,最终形成了一个并行光线追踪演示。这个演示包含许多活动的部件,而且不幸的是它不是最容易处理的,但希望这能让您体验一下在 Web 上使用线程和 Rust 的 Wasm 的感受。

构建演示

线程 WebAssembly 的主要难题之一是 Rust 没有发布启用线程支持的预编译目标(例如标准库)。这意味着您需要使用适当的 rustc 标志(即 -C target-feature=+atomics,+bulk-memory,+mutable-globals)重新编译标准库。请注意,这需要 nightly Rust 工具链。

为此,您可以使用 Cargo 读取的 RUSTFLAGS 环境变量

export RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals'

要重新编译标准库,建议使用 Cargo 的 -Zbuild-std 功能

cargo build --target wasm32-unknown-unknown -Z build-std=panic_abort,std

请注意,您也可以通过 .cargo/config.toml 配置此项

[unstable]
build-std = ['std', 'panic_abort']

[build]
target = "wasm32-unknown-unknown"
rustflags = '-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals'

此后,cargo build 应该会生成一个启用线程的 WebAssembly 文件,并且标准库也会被正确编译。

最后一步是像往常一样运行 wasm-bindgen,并且 wasm-bindgen 不需要额外的配置即可使用线程。例如,您可以继续通过 wasm-pack 运行它。

运行演示

目前,需要使用 --target no-modules--target web 标志与 wasm-bindgen 一起运行线程代码。这是因为 WebAssembly 文件导入内存而不是导出它,因此我们需要在此时挂钩 wasm 模块的初始化以提供适当的内存对象。此演示使用 --target no-modules,因为 Firefox 不支持 Web Worker 中的模块。

使用 --target no-modules,您将能够在每个 Web Worker 中使用 importScripts 来导入 wasm-bindgen 生成的 shim JS,以及使用主线程中的共享内存实例调用 wasm_bindgen 初始化函数。预期的用法是,主线程上的 WebAssembly 将其内存对象发布到所有其他线程以进行实例化。

注意事项

不幸的是,目前在 Web 上使用线程运行 Wasm 有许多注意事项,尽管其中一些仅特定于 wasm-bindgen。这些是一些需要考虑和注意的事项,尽管我们一直在寻找改进的方法,所以如果您有任何想法,请提交 issue!

  • 浏览器中的主线程无法阻塞。这意味着如果您在主线程上运行 WebAssembly 代码,则永远不能阻塞,这意味着您甚至无法获取互斥锁。这是 Web 上一个极其难以处理的限制,尽管一种解决方法是在 Web Worker 中专门运行 Wasm,并在主线程上运行 JS。可以在所有线程上运行相同的 wasm,但是您需要非常警惕与主线程的同步。

  • 设置线程环境目前有些不顺畅,感觉不太流畅。例如,--target bundler 不受支持,并且需要在主线程和工作线程上使用非常特殊的垫片。虽然这些可以工作,但由于没有标准的将 Web Worker 作为 Wasm 线程启动的方式,因此它们有些脆弱。

  • 目前没有标准的“线程”概念。例如,标准库没有可行的途径来实现 std::thread 模块。因此,没有线程退出的概念,并且 TLS 析构函数永远不会运行。我们公开了一个帮助函数 __wbindgen_thread_destroy,用于释放线程堆栈和 TLS。如果调用它,它必须是给定线程从 Wasm 模块调用的最后一个函数。

  • 第一个线程之后启动的任何线程可能会在其初始化例程中隐式地尝试阻塞。这是我们设置线程堆栈和 TLS 空间的方式所引入的限制。这意味着如果您在工作线程中运行 Wasm 模块之后尝试在主线程中运行它,可能会失败。

  • 执行 WebAssembly 代码的 Web Worker 无法接收来自 JS 的事件。Web Worker 必须完全返回到浏览器(并且最好偶尔这样做)才能接收 JS 消息等。这意味着像 rayon 线程池这样的常见范例不能直接应用于 Web。Web 的意图是所有长期阻塞都发生在浏览器本身,而不是发生在每个线程中,但生态系统中许多利用线程的 crate 不一定以这种方式设计。

这些警告很大程度上是从 Web 平台本身继承的,在为线程设计应用程序时,考虑它们非常重要。由于这些限制,您不太可能直接从货架上拿一个 crate 并“直接使用它”。您需要提前仔细规划,并确保诸如此类的陷阱不会在未来造成问题。如前所述,我们始终在积极开发这项支持,因此如果大家有关于如何改进的想法,或者如果 Web 标准发生变化,我们将尝试更新此文档!

浏览器要求

目前,此演示应该可以在最新的 Firefox 和 Chrome 版本中工作,其他浏览器也可能会效仿。请注意,线程和 SharedArrayBuffer 需要设置 HTTP 标头才能正常工作。有关更多信息,请参阅 MDN 上关于“安全要求”的文档以及 Firefox 的发布博客文章。这意味着在本地开发期间,您需要适当地配置您的 Web 服务器或在您的浏览器中启用一种解决方法。