Chrome 70 中的 WebAssembly 线程现已可试用

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

Alex Danilo

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

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

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

线程?那么工作器呢?

自 2012 年以来,浏览器在 Chrome 4 中一直通过 Web Workers 支持并行处理;事实上,听到“在主线程上”等术语属于正常现象。但是,Web Worker 并不在它们之间共享可变数据,而是依靠消息传递来进行通信。事实上,Chrome 会为其中的每一个容器分配一个新的 V8 引擎(称为隔离引擎)。隔离既不会共享编译的代码,也不会共享 JavaScript 对象,因此不能共享 pthread 等可变数据。

另一方面,WebAssembly 线程是可以共享同一 Wasm 内存的线程。共享内存的底层存储是通过 SharedArrayBuffer 完成的,这是一个 JavaScript 基元,可在工作器之间并发共享单个 ArrayBuffer 的内容。每个 WebAssembly 线程都在 Web 工作器中运行,但其共享 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() 函数开头,该函数声明了 fg_valbg_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 工作器,与每个工作器共享该模块(在本例中为 2 个),并且每当调用 pthread_create() 时都会使用这些模块。每个工作器都会使用相同的内存实例化 Wasm 模块,以便进行协作。V8 在 7.0 中的最新更改共享了在工作器之间传递的 Wasm 模块的已编译原生代码,这使得大型应用也可以扩展到许多工作器。请注意,确保线程池大小等于应用所需的最大线程数,否则线程创建可能会失败。同时,如果线程池大小过大,则会创建不必要的 Web 工作器,这些 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>

若要尝试此页面,您需要运行某种形式的 Web 服务器并从浏览器加载该页面。这将使 WebAssembly 模块加载并运行。打开开发者工具会显示运行的输出,您应该会在控制台中看到如下输出图片:

fibonacci 程序的控制台输出

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

通过源试用在现场进行测试

出于开发目的,可以在浏览器中开启实验性标志来试用线程,但如果您想在现场测试您的应用,可以执行所谓的“源试用”

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

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

如果您想了解 4 个并行运行的线程能为 ASCII 图片提供哪些效果,那么也必须查看此演示

向我们提供反馈

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

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