文章目录

JavaScript Atomics.waitAsync非阻塞等待SharedArrayBuffer

发布于 2026-05-03 11:13:25 · 浏览 19 次 · 评论 0 条

JavaScript Atomics.waitAsync非阻塞等待SharedArrayBuffer

JavaScript 主线程是单线程的,这意味着如果在主线程中执行耗时操作会阻塞页面渲染,导致用户界面卡顿。在使用 SharedArrayBuffer 进行多线程(Web Workers)共享内存操作时,传统的 Atomics.wait() 会直接挂起当前线程,如果在主线程调用,页面会彻底冻结。Atomics.waitAsync() 解决了这个问题,它允许线程等待共享内存的变化而不阻塞执行流,非常适合用于高性能的并发任务调度。


准备工作:配置服务器安全策略

由于安全原因,现代浏览器默认禁用 SharedArrayBuffer,除非页面处于“跨域隔离”环境。你需要设置特定的 HTTP 响应头才能使用相关功能。

  1. 打开你的 Web 服务器配置文件(如 Nginx 的 .conf 文件或 Node.js 的 Express 中间件)。

  2. 添加以下两个响应头到配置中:

    响应头名称
    Cross-Origin-Embedder-Policy require-corp
    Cross-Origin-Opener-Policy same-origin
  3. 重启 Web 服务器使配置生效。

  4. 确认页面加载时无报错,且 typeof SharedArrayBuffer !== 'undefined'


步骤一:创建主线程逻辑

主线程负责创建共享内存,并将其发送给 Worker,随后在适当的时候修改内存数据并通知 Worker。

  1. 创建一个名为 main.js 的文件。

  2. 初始化一块 SharedArrayBuffer,大小为 4 字节(足以存储一个 32 位整数)。

  3. 创建一个 Int32Array 视图来操作这块内存。

  4. 实例化 Web Worker。

  5. 编写发送内存给 Worker 的代码,并在 2 秒后修改内存值并通知。

    // main.js
    
    // 1. 创建共享内存缓冲区(4字节,用于存一个整数)
    const sharedBuffer = new SharedArrayBuffer(4);
    const sharedArray = new Int32Array(sharedBuffer);
    
    // 初始值为 0,表示“未就绪”
    sharedArray[0] = 0;
    
    console.log('[Main] 内存已初始化,当前值:', sharedArray[0]);
    
    // 2. 创建 Worker
    const worker = new Worker('worker.js');
    
    // 3. 将共享内存的所有权转移给 Worker(实际上是复制引用)
    worker.postMessage({ sharedBuffer }, [sharedBuffer]);
    
    // 4. 模拟异步操作,2秒后唤醒 Worker
    setTimeout(() => {
        console.log('[Main] 准备修改内存值并唤醒 Worker...');
    
        // 更新共享内存的值
        Atomics.store(sharedArray, 0, 1);
    
        // 通知正在等待的 Worker
        // 参数:数组, 索引, 等待唤醒的线程数量
        Atomics.notify(sharedArray, 0, 1);
    
        console.log('[Main] 完成通知,当前值:', sharedArray[0]);
    }, 2000);

步骤二:编写 Worker 线程逻辑

Worker 线程接收共享内存,并使用 Atomics.waitAsync 等待主线程的信号。与 wait 不同,这里等待是非阻塞的,Worker 可以在等待期间处理其他任务。

  1. 创建一个名为 worker.js 的文件。

  2. 监听主线程发送的 message 事件以获取共享内存。

  3. 调用 Atomics.waitAsync 进行非阻塞等待。

  4. 处理返回的 Promise 对象,一旦主线程发送通知,Promise 将 resolve。

    // worker.js
    
    self.onmessage = function(e) {
        const sharedArray = new Int32Array(e.data.sharedBuffer);
    
        console.log('[Worker] 开始等待共享内存变化...');
    
        // 使用 waitAsync 进行非阻塞等待
        // 参数:数组, 索引, 期望值(如果不等于此值则立即返回)
        const result = Atomics.waitAsync(sharedArray, 0, 0);
    
        console.log('[Worker] waitAsync 已调用,不阻塞,我可以做别的事情。');
    
        // 检查等待结果
        if (result.async) {
            // 如果是异步等待,result.value 是一个 Promise
            result.value.then((value) => {
                console.log('[Worker] 收到通知!Promise resolved.');
                console.log('[Worker] Atomics.waitAsync 返回的值:', value);
                console.log('[Worker] 当前内存值:', Atomics.load(sharedArray, 0));
            }).catch(err => {
                console.error('[Worker] 出错了:', err);
            });
        } else {
            // 同步返回(极少数情况,例如主线程不匹配或已经在主线程误用)
            console.log('[Worker] 同步返回:', result.value);
        }
    };

步骤三:运行并验证流程

通过 HTML 页面加载主线程脚本,观察控制台输出,验证非阻塞特性。

  1. 创建一个 index.html 文件。

  2. 引入 main.js

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Atomics.waitAsync Demo</title>
    </head>
    <body>
        <h1>打开控制台查看日志</h1>
        <script src="main.js"></script>
    </body>
    </html>
  3. 启动本地服务器,访问 index.html

  4. 打开浏览器开发者工具的 Console(控制台)面板。

  5. 观察日志输出顺序。

预期输出如下:

[Main] 内存已初始化,当前值: 0
[Worker] 开始等待共享内存变化...
[Worker] waitAsync 已调用,不阻塞,我可以做别的事情。
[Main] 准备修改内存值并唤醒 Worker...
[Main] 完成通知,当前值: 1
[Worker] 收到通知!Promise resolved.
[Worker] Atomics.waitAsync 返回的值: "ok"
[Worker] 当前内存值: 1

核心机制解析

Atomics.waitAsync 的核心在于它将“等待”行为封装成了一个 Promise,从而释放了事件循环。为了更直观地理解主线程与 Worker 之间的数据流向与控制权转移,请参考以下流程图:

sequenceDiagram participant Main as 主线程 participant Mem as SharedArrayBuffer participant Worker as Worker 线程 Note over Main, Mem: 初始化阶段 Main->>Mem: 写入初始值 0 Main->>Worker: 传递 Buffer 引用 Note over Worker, Mem: 等待阶段 Worker->>Mem: 读取值 (0) Worker->>Worker: 调用 waitAsync(0) Note right of Worker: 生成 Pending Promise
线程继续执行,不阻塞 Worker-->>Main: (此时 Worker 闲置或处理其他任务) Note over Main, Mem: 通知阶段 Main->>Mem: 写入新值 1 Main->>Mem: 调用 notify() Note over Worker, Mem: 唤醒阶段 Mem-->>Worker: Promise Resolve ("ok") Worker->>Mem: 读取值 (1) Worker->>Worker: 执行 .then() 回调

关键注意事项与最佳实践

在使用 Atomics.waitAsync 时,务必遵守以下规则以确保程序正确且高效。

注意事项 说明 解决方案
COOP/COEP 头部 缺少安全头部会导致 SharedArrayBuffer 未定义。 配置服务器响应头 Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
期望值匹配 waitAsync 的第三个参数是“期望值”。如果内存当前值不等于期望值,等待立即结束。 确保 store 操作改变值之前,waitAsync 已经处于等待状态且期望值一致。
索引越界 提供的索引超出了 TypedArray 的长度。 确保索引在 0array.length - 1 之间。
非主线程限制 虽然 waitAsync 是非阻塞的,但在某些浏览器环境中,主线程上调用仍然受限于严格的策略。 建议在 Web Worker 中使用 waitAsync,主线程仅负责 notify
  1. 使用 result.async 属性来判断等待是否为异步模式。
  2. 避免Promise 回调中执行极其耗时的同步计算,否则仍会拖慢 Worker 响应后续消息。
  3. 利用 Atomics.notify 的第三个参数(计数)来精确控制唤醒多少个等待中的 Worker,实现“只唤醒一个”或“广播唤醒所有”的策略。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文