WebAssembly 线程支持已在 Chrome 70 中以来源试用版的形式发布。
WebAssembly (Wasm) 支持编译使用 C++ 和其他语言编写的代码,以便在 Web 上运行。原生应用的一项非常实用的功能是能够使用线程,线程是并行计算的基本功能。大多数 C 和 C++ 开发者都熟悉 pthreads,它是用于管理应用中线程的标准化 API。
WebAssembly 社区群组一直致力于将线程引入到 Web 中,以实现真正的多线程应用。作为这项工作的一部分,V8 为 WebAssembly 引擎中的线程实现了必要的支持,可通过源试用获得。借助源试用,开发者可以在新 Web 功能完全标准化之前对其进行实验。这样一来,我们就可以从大胆尝试的开发者那里收集真实反馈,这对验证和改进新功能至关重要。
Chrome 70 版本支持 WebAssembly 线程,我们建议有兴趣的开发者开始使用这些线程并向我们提供反馈。
线程?那 Worker 呢?
自 2012 年 Chrome 4 起,浏览器就支持通过 Web Worker 实现并行处理;事实上,听到“在主线程上”等术语很正常。不过,Web Worker 不会在彼此之间共享可变数据,而是依赖于消息传递进行通信。事实上,Chrome 会为每个 isolate 分配一个新的 V8 引擎。隔离容器既不共享已编译的代码也不共享 JavaScript 对象,因此它们无法共享可变数据(如 pthread)。
另一方面,WebAssembly 线程是可以共享相同 Wasm 内存的线程。共享内存的底层存储是通过 SharedArrayBuffer 实现的,这是一种 JavaScript 基元,允许在多个工作器之间同时共享单个 ArrayBuffer 的内容。每个 WebAssembly 线程都在 Web Worker 中运行,但由于它们共享 Wasm 内存,因此其工作方式与在原生平台上的工作方式非常相似。这意味着,使用 Wasm 线程的应用负责管理对共享内存的访问,就像在任何传统线程应用中一样。许多使用 pthread 编写的现有代码库都是用 C 或 C++ 编写的,这些库可以编译为 Wasm 并在真实线程模式下运行,从而允许更多核心同时处理同一数据。
一个简单示例
下面是一个使用线程的简单“C”程序示例。
#include <pthread.h>
#include <stdio.h>
// Calculate Fibonacci numbers shared function
int fibonacci(int iterations) {
int val = 1;
int last = 0;
if (iterations == 0) {
return 0;
}
for (int i = 1; i < iterations; i++) {
int seq;
seq = val + last;
last = val;
val = seq;
}
return val;
}
// Start function for the background thread
void *bg_func(void *arg) {
int *iter = (void *)arg;
*iter = fibonacci(*iter);
return arg;
}
// Foreground thread and main entry point
int main(int argc, char *argv[]) {
int fg_val = 54;
int bg_val = 42;
pthread_t bg_thread;
// Create the background thread
if (pthread_create(&bg_thread, NULL, bg_func, &bg_val)) {
perror("Thread create failed");
return 1;
}
// Calculate on the foreground thread
fg_val = fibonacci(fg_val);
// Wait for background thread to finish
if (pthread_join(bg_thread, NULL)) {
perror("Thread join failed");
return 2;
}
// Show the result from background and foreground threads
printf("Fib(42) is %d, Fib(6 * 9) is %d\n", bg_val, fg_val);
return 0;
}
该代码以 main()
函数开头,该函数声明了 2 个变量 fg_val
和 bg_val
。还有一个名为 fibonacci()
的函数,此示例中的两个线程都将调用该函数。main()
函数使用 pthread_create()
创建一个后台线程,该线程的任务是计算与 bg_val
变量的值对应的斐波那契数序列值。与此同时,在前台线程中运行的 main()
函数会为 fg_val
变量计算该值。后台线程运行完毕后,系统会输出结果。
编译以支持线程
首先,您应安装 emscripten SDK,最好是 1.38.11 或更高版本。如需构建启用了线程的示例代码以在浏览器中运行,我们需要向 emscripten emcc 编译器传递一些额外的标志。我们的命令行如下所示:
emcc -O2 -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=2 -o test.js test.c
命令行参数“-s USE_PTHREADS=1
”会为编译后的 WebAssembly 模块启用线程支持,参数“-s PTHREAD_POOL_SIZE=2
”会指示编译器生成一个包含两个 (2) 个线程的线程池。
程序运行时,会在后台加载 WebAssembly 模块,为线程池中的每个线程创建一个 Web Worker,并与每个工作器共享该模块(在本例中为 2),每当调用 pthread_create()
时都会使用这些工作器。每个工作器都会使用同一内存实例化 Wasm 模块,以便它们进行协作。V8 7.0 中的最新变更共享了在工作器之间传递的 Wasm 模块的已编译原生代码,这使得即使非常大型的应用也能扩展到许多工作器。请注意,请务必确保线程池大小等于应用所需的线程数量上限,否则线程创建可能会失败。同时,如果线程池大小过大,您将创建不必要的 Web Worker,这些 Worker 除了占用内存之外,什么也不会做。
如何试用
如需快速测试我们的 WebAssembly 模块,最快的方法是在 Chrome 70 及更高版本中开启实验性 WebAssembly 线程支持。在浏览器中前往网址 about://flags
,如下所示:
接下来,找到实验性 WebAssembly 线程设置,该设置如下所示:
如下所示,将设置更改为已启用,然后重启浏览器。
浏览器重启后,我们可以尝试使用仅包含以下内容的最小 HTML 页面加载线程化 WebAssembly 模块:
<!DOCTYPE html>
<html>
<title>Threads test</title>
<body>
<script src="test.js"></script>
</body>
</html>
如需试用此页面,您需要运行某种形式的 Web 服务器,并从浏览器加载该服务器。这会使 WebAssembly 模块加载并运行。打开 DevTools 后,您会看到运行的输出,并且您应该会在控制台中看到类似下图的输出图片:
包含线程的 WebAssembly 程序已成功执行!我们建议您按照上述步骤尝试使用自己的线程应用。
通过源试用在现场进行测试
在浏览器中启用实验性标志来试用线程对于开发目的来说是可以的,但如果您想在实际环境中测试应用,可以使用所谓的源代码试用版。
通过来源试用,您可以获取与您的网域相关联的测试令牌,以便向用户试用实验性功能。然后,您可以部署应用,并希望它能在支持您要测试的功能的浏览器(在本例中为 Chrome 70 及更高版本)中正常运行。如需获取自己的令牌以运行来源试用,请使用此处的表单提交申请。
我们使用来源试用令牌托管了上面的简单示例,因此您无需构建任何内容即可亲自试用。
如果您想了解并行运行的 4 个线程对 ASCII 艺术图片有何作用,那么您还必须查看此演示!
向我们提供反馈
WebAssembly 线程是一项非常实用的新基元,可用于将应用移植到 Web 平台。现在,您可以在 WebAssembly 环境中运行需要 pthreads 支持的 C 和 C++ 应用和库。
我们希望试用此功能的开发者提供反馈,这有助于我们制定标准化流程并验证其实用性。发送反馈的最佳方式是报告问题和/或参与 WebAssembly 社区群组中的标准化流程。