WebAssembly 线程已准备好在 Chrome 70 中试用

Chrome 70 中已通过源试用提供 WebAssembly 线程支持。

Alex Danilo

WebAssembly (Wasm) 支持将使用 C++ 和其他语言编写的代码编译为可在 Web 上运行的代码。原生应用的一个非常实用的功能是能够使用线程,即并行计算的原语。大多数 C 和 C++ 开发者都熟悉 pthreads,这是一种用于在应用中管理线程的标准化 API。

WebAssembly 社区小组一直在努力将线程引入 Web,以实现真正的多线程应用。作为这项工作的一部分,V8 在 WebAssembly 引擎中实现了对线程的必要支持,可通过源试用获得。源试用让开发者可以在新 Web 功能完全标准化之前对其进行实验。这样,我们就可以从勇敢的开发者那里收集实际反馈,这对于验证和改进新功能至关重要。

Chrome 70 版本支持 WebAssembly 线程,我们鼓励感兴趣的开发者开始使用它们并向我们提供反馈。

Threads?Worker 呢?

自 2012 年 Chrome 4 发布以来,浏览器就通过 Web Worker 支持并行处理;事实上,听到“在主线程上”等术语是很正常的。不过,Web Worker 之间不会共享可变数据,而是依靠消息传递进行通信。事实上,Chrome 会为每个隔离区分配一个新的 V8 引擎。隔离区既不共享已编译的代码,也不共享 JavaScript 对象,因此它们无法像 pthread 一样共享可变数据。

另一方面,WebAssembly 线程是可以共享同一 Wasm 内存的线程。共享内存的底层存储是通过 SharedArrayBuffer 实现的,这是一种 JavaScript 基元,允许在工作器之间同时共享单个 ArrayBuffer 的内容。每个 WebAssembly 线程都在 Web Worker 中运行,但其共享的 Wasm 内存使它们能够像在原生平台上一样工作。这意味着,使用 Wasm 线程的应用负责管理对共享内存的访问,就像在任何传统线程应用中一样。许多现有的 C 或 C++ 代码库都使用 pthreads,这些库可以编译为 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,并与每个 Worker 共享该模块(在本例中为 2 个),每当调用 pthread_create() 时,都会使用这些 Worker。每个工作器都会使用相同的内存实例化 Wasm 模块,从而实现协作。V8 在 7.0 中的最新更改会共享在工作器之间传递的 Wasm 模块的已编译原生代码,这使得即使是非常大型的应用也可以扩缩到许多工作器。请注意,确保线程池大小等于应用所需的最大线程数是有意义的,否则线程创建可能会失败。 同时,如果线程池大小过大,您将创建不必要的 Web Worker,这些 Web Worker 除了占用内存之外,什么都不做。

如何试用

测试 WebAssembly 模块的最快方法是在 Chrome 70 及更高版本中开启实验性 WebAssembly 线程支持。在浏览器中前往网址 about://flags,如下所示:

Chrome flag 页面

接下来,找到实验性 WebAssembly 线程设置,如下所示:

WebAssembly 线程设置

将设置更改为已启用(如下所示),然后重启浏览器。

已启用 WebAssembly 线程设置

浏览器重启后,我们可以尝试使用一个仅包含以下内容的极简 HTML 页面来加载多线程 WebAssembly 模块:

<!DOCTYPE html>
<html>
  <title>Threads test</title>
  <body>
    <script src="test.js"></script>
  </body>
</html>

如需试用此页面,您需要运行某种形式的网络服务器,并从浏览器加载该页面。这将导致 WebAssembly 模块加载并运行。打开开发者工具后,您会看到运行输出,控制台中应该会显示类似下图的输出:

fibonacci 程序的控制台输出

我们的多线程 WebAssembly 程序已成功执行!我们建议您按照上述步骤尝试运行自己的线程化应用。

通过源试用进行现场测试

在浏览器中开启实验性标志来试用线程,这对于开发目的来说是可以的,但如果您想在实际环境中测试应用,可以使用所谓的源试用。

通过源试用,您可以获取与您的网域关联的测试令牌,从而与用户一起试用实验性功能。然后,您可以部署应用,并期望它在支持您要测试的功能的浏览器(在本例中为 Chrome 70 及更高版本)中运行。如需获取自己的令牌以运行源试用,请使用此处的申请表单。

我们已使用源试用令牌托管了上述简单示例,因此您可以自行试用,而无需构建任何内容。

如果您想了解 4 个并行运行的线程可以为 ASCII 艺术做什么,那么您也必须观看此演示!

向我们提供反馈

WebAssembly 线程是一种非常实用的新基元,可用于将应用移植到 Web。现在,可以在 WebAssembly 环境中运行需要 pthreads 支持的 C 和 C++ 应用和库。

我们希望试用此功能的开发者提供反馈,因为这有助于我们了解标准化流程并验证其有用性。发送反馈的最佳方式是报告问题和/或参与 WebAssembly 社区群组中的标准化流程。