并行光线追踪

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

这是一个使用 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 不支持工作单元中的模块。

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

注意事项

不幸的是,目前在 Web 上使用线程运行 Wasm 有许多注意事项,尽管其中一些是 wasm-bindgen 特有的。以下是一些需要考虑和注意的事项,尽管我们一直在寻求改进,因此如果你有任何想法,请提出问题!

  • 浏览器中的主线程不能阻塞。这意味着如果你在主线程上运行 WebAssembly 代码,你永远不能阻塞,这意味着你不能像获取互斥锁那样做太多事情。这是 Web 上一个极其难以处理的限制,尽管一种解决方法是在 Web 工作单元中独占地运行 Wasm,并在主线程上运行 JS。可以在所有线程上运行相同的 wasm,但你需要非常警惕与主线程的同步。

  • 设置线程环境有点怪异,并且今天感觉不流畅。例如,不支持 --target bundler,并且在主线程和工作单元线程上都需要非常特定的 shim。这些可以处理,但有点脆弱,因为没有标准方法可以将 Web 工作单元作为 Wasm 线程启动。

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

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

  • 执行 WebAssembly 代码的 Web 工作单元无法从 JS 接收事件。Web 工作单元必须完全返回到浏览器(理想情况下应该偶尔这样做)才能接收 JS 消息等。这意味着像 rayon 线程池这样的常见范例不能直接应用于 Web。Web 的意图是所有长期阻塞都发生在浏览器本身中,而不是在每个线程中,但是生态系统中的许多利用线程的 crate 不一定是这样设计的。

这些注意事项大多继承自 Web 平台本身,在为线程设计应用程序时,务必考虑它们。由于这些限制,您不太可能直接拿出一个现成的 crate 就“直接使用”。您需要仔细提前规划,并确保诸如此类的陷阱不会在未来造成问题。不过,正如之前提到的,我们一直在积极开发对线程的支持,所以如果大家有关于如何改进的想法,或者如果 Web 标准发生变化,我们会尝试更新本文档!

浏览器要求

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