并行光线追踪
这是一个使用 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` 运行它。
运行演示
目前,需要使用 `wasm-bindgen` 的 `--target no-modules` 或 `--target web` 标志来运行线程化代码。这是因为 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 Workers 无法接收来自 JS 的事件。Web Worker 必须完全返回到浏览器(并且理想情况下应该偶尔这样做)才能接收 JS 消息等。这意味着像 rayon 线程池这样的常见范式不能直接应用于 Web。Web 的意图是在浏览器本身中进行所有长期阻塞,而不是在每个线程中,但生态系统中利用线程的许多板条箱不一定以这种方式设计。
这些注意事项在很大程度上都是从 Web 平台本身继承而来的,在设计线程应用程序时需要考虑它们。由于这些限制,您不太可能从货架上拉出一个板条箱并“直接使用它”。您需要仔细计划并确保将来不会出现此类问题。不过,正如之前提到的,我们一直在积极开发这种支持,因此如果大家有关于如何改进的想法,或者 Web 标准发生变化,我们将尝试更新此文档!
浏览器要求
此演示目前应该在最新的 Firefox 和 Chrome 版本中运行,其他浏览器也可能会效仿。请注意,线程和 SharedArrayBuffer
需要设置 HTTP 标头才能正常工作。有关更多信息,请参阅 MDN 上的文档 中的“安全要求”部分,以及 Firefox 的推出博客文章。这意味着在本地开发期间,您需要适当地配置您的 Web 服务器或在您的浏览器中启用解决方法。