WEBKT

Node.js 中 Atomics 的底层探秘:wait() 与 notify() 的实现原理

88 0 0 0

1. 为什么需要 Atomics?

2. Atomics.wait() 和 Atomics.notify() 的作用

3. 底层实现原理:futex

3.1 futex 系统调用

4. 跨平台实现

5. 示例代码

6. 注意事项

7. 总结

你好!咱们今天来聊点硬核的,深入 Node.js 的底层,一起探究 Atomics.wait()Atomics.notify() 这两个原子操作函数的实现原理。相信你对多线程编程、共享内存这些概念并不陌生,那么在 Node.js 中,如何利用 Atomics 模块来实现线程间的同步和通信呢?这背后又隐藏着怎样的机制?别急,咱们一步步揭开它们的神秘面纱。

1. 为什么需要 Atomics?

在传统的 JavaScript 单线程模型中,我们通常不需要考虑线程同步的问题。但是,随着 Node.js worker_threads 模块的引入,我们可以在 Node.js 中创建多个线程,这些线程可以共享内存(通过 SharedArrayBuffer)。共享内存带来了便利,但也带来了数据竞争的风险。想象一下,多个线程同时读写同一块内存区域,如果没有适当的同步机制,很容易导致数据错乱、程序崩溃。

Atomics 模块正是为了解决这个问题而生的。它提供了一组原子操作函数,可以保证对共享内存的读写操作是原子性的,即不可分割的。这意味着,一个线程在执行原子操作时,其他线程无法中断它,从而避免了数据竞争。

2. Atomics.wait() 和 Atomics.notify() 的作用

Atomics.wait()Atomics.notify()Atomics 模块中两个非常重要的函数,它们用于实现线程间的等待和唤醒机制,类似于其他语言中的条件变量(Condition Variable)。

  • Atomics.wait(typedArray, index, value, timeout): 让当前线程在 typedArrayindex 位置上等待,直到被 Atomics.notify() 唤醒,或者超时。value 参数用于检查 typedArray[index] 的值是否仍然与预期值相等,如果不相等,则立即返回 'not-equal'timeout 参数指定等待的超时时间(毫秒)。
  • Atomics.notify(typedArray, index, count): 唤醒在 typedArrayindex 位置上等待的一个或多个线程。count 参数指定要唤醒的线程数量,默认为 1。

这两个函数通常配合使用,一个线程调用 Atomics.wait() 进入等待状态,另一个线程调用 Atomics.notify() 唤醒它。这种机制可以实现线程间的同步和协作。

3. 底层实现原理:futex

Atomics.wait()Atomics.notify() 的底层实现依赖于操作系统提供的同步原语,其中最关键的就是 futex(Fast Userspace Mutexes)。

futex 是 Linux 内核提供的一种快速用户空间互斥锁机制。它允许用户空间的线程在没有竞争的情况下快速获取和释放锁,只有在发生竞争时才需要进入内核态进行处理。这种机制大大提高了同步操作的效率。

Atomics.wait()Atomics.notify() 的核心思想就是利用 futex 实现线程的等待和唤醒:

  • Atomics.wait(): 当一个线程调用 Atomics.wait() 时,它首先检查 typedArray[index] 的值是否等于预期值。如果相等,则通过系统调用进入内核态,将当前线程添加到与 typedArray[index] 关联的等待队列中,并阻塞当前线程。如果不相等,则直接返回。
  • Atomics.notify(): 当一个线程调用 Atomics.notify() 时,它通过系统调用进入内核态,找到与 typedArray[index] 关联的等待队列,并唤醒其中的一个或多个线程。

3.1 futex 系统调用

在 Linux 中,futex 的系统调用接口是 futex() 函数:

int futex(int *uaddr, int futex_op, int val, const struct timespec *timeout, int *uaddr2, int val3);
  • uaddr: 指向用户空间内存地址的指针,通常是一个整数变量,用于表示锁的状态。
  • futex_op: 指定 futex 操作的类型,例如 FUTEX_WAIT(等待)、FUTEX_WAKE(唤醒)等。
  • val: 用于比较或传递值的参数,具体含义取决于 futex_op
  • timeout: 指定等待的超时时间。
  • uaddr2val3: 用于一些高级的 futex 操作。

Node.js 的 Atomics.wait()Atomics.notify() 内部会调用 futex() 函数,并根据不同的平台和操作系统版本进行适配。

4. 跨平台实现

虽然 futex 是 Linux 的特性,但 Atomics.wait()Atomics.notify() 可以在其他平台上使用。这是因为 Node.js 会根据不同的操作系统提供相应的实现:

  • Linux: 使用 futex。
  • Windows: 使用 WaitOnAddressWakeByAddressSingle/All 函数,这些函数提供了类似 futex 的功能。
  • macOS: 使用 ulock_waitulock_wake 函数,或者使用 pthread 的条件变量。

Node.js 内部通过条件编译和抽象层,屏蔽了不同平台的差异,使得开发者可以使用统一的 Atomics API 进行跨平台开发。

5. 示例代码

下面是一个简单的示例,演示了如何使用 Atomics.wait()Atomics.notify() 实现线程间的同步:

// main.js
const { Worker, isMainThread, workerData, parentPort } = require('worker_threads');
const { Buffer } = require('buffer');
if (isMainThread) {
// 创建一个共享内存
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);
// 创建一个 worker 线程
const worker = new Worker(__filename, { workerData: { sharedBuffer } });
// 主线程等待 worker 线程完成
console.log('Main thread: Waiting...');
Atomics.wait(sharedArray, 0, 0); // 等待 sharedArray[0] 的值变为非 0
console.log('Main thread: Notified!');
// 输出共享内存的值
console.log('Shared array:', sharedArray[0]);
} else {
// worker 线程
const { sharedBuffer } = workerData;
const sharedArray = new Int32Array(sharedBuffer);
// 模拟一些耗时操作
setTimeout(() => {
// 修改共享内存的值
Atomics.store(sharedArray, 0, 123);
// 通知主线程
console.log('Worker thread: Notifying...');
Atomics.notify(sharedArray, 0, 1);
}, 1000);
}

在这个例子中,主线程创建了一个 SharedArrayBuffer 和一个 worker 线程。worker 线程执行一些耗时操作后,修改共享内存的值,并通过 Atomics.notify() 通知主线程。主线程在 Atomics.wait() 处等待,直到被 worker 线程唤醒。

6. 注意事项

在使用 Atomics.wait()Atomics.notify() 时,需要注意以下几点:

  • Atomics.wait() 只能在 worker 线程中调用,不能在主线程中调用。否则会抛出异常。
  • Atomics.wait()Atomics.notify() 操作的是 Int32ArrayBigInt64Array 类型的数据。
  • Atomics.wait()value 参数用于进行比较,确保在等待期间共享内存的值没有被其他线程修改。如果值被修改了,Atomics.wait() 会立即返回 'not-equal'
  • Atomics.notify() 可以唤醒多个等待的线程,count 参数指定了要唤醒的线程数量。
  • 要特别注意死锁问题。不正确的等待和唤醒顺序可能导致死锁。

7. 总结

通过今天的探讨,我们深入了解了 Node.js 中 Atomics.wait()Atomics.notify() 的底层实现原理,以及它们与操作系统底层同步原语(如 futex)之间的关系。希望你对 Node.js 的多线程编程有了更深入的理解。掌握这些底层知识,可以帮助你更好地利用 Node.js 的多线程特性,编写出更高效、更可靠的程序。

记住,Atomics 模块是 Node.js 多线程编程的基石,而 Atomics.wait()Atomics.notify() 则是实现线程间同步和通信的关键。虽然底层原理比较复杂,但只要理解了它们的核心思想,就能在实际开发中灵活运用,解决各种多线程编程的难题。

如果你在使用的过程中遇到任何问题, 欢迎随时交流!

NodeJS老司机 Node.jsAtomics多线程

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/7936