数据由结构化克隆算法进行复制。引用自 Mozilla:
它通过递归输入对象来进行克隆,同时保持之前访问过的引用的映射,以避免无限遍历循环。该算法不复制函数、错误、属性描述符或原型链。还需要注意的是,以这种方式复制对象与使用 JSON 不同,因为它可以包含循环引用和类型化数组,而 JSON 不能。
由于能够复制类型化数组,该算法可以在线程之间共享内存。
在线程之间共享内存
人们可能会说像 cluster 或 child_process 这样的模块在很久以前就开始使用线程了。这话对,也不对。
cluster 模块可以创建多个节点实例,其中一个主进程在它们之间对请求进行路由。集群能够有效地增加服务器的吞吐量;但是我们不能用 cluster 模块生成一个单独的线程。
人们倾向于用 PM2 这样的工具来集中管理他们的程序,而不是在自己的代码中手动执行,如果你有兴趣,可以研究一下如何使用 cluster 模块。
child_process 模块可以生成任何可执行文件,无论它是否是用 JavaScript 写的。它和 worker_threads 非常相似,但缺少后者的几个重要功能。
具体来说 thread workers 更轻量,并且与其父线程共享相同的进程 ID。它们还可以与父线程共享内存,这样可以避免对大的数据负载进行序列化,从而更有效地来回传递数据。
现在让我们看一下如何在线程之间共享内存。为了共享内存,必须将 ArrayBuffer 或 SharedArrayBuffer 的实例作为数据参数发送到另一个线程。
这是一个与其父线程共享内存的 worker:
import { parentPort } from 'worker_threads'; parentPort.on('message', () => { const numberOfElements = 100; const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * numberOfElements); const arr = new Int32Array(sharedBuffer); for (let i = 0; i < numberOfElements; i += 1) { arr[i] = Math.round(Math.random() * 30); } parentPort.postMessage({ arr }); });
首先,我们创建一个 SharedArrayBuffer,其内存需要包含100个32位整数。接下来创建一个 Int32Array 实例,它将用缓冲区来保存其结构,然后用一些随机数填充数组并将其发送到父线程。
在父线程中:
import path from 'path'; import { runWorker } from '../run-worker'; const worker = runWorker(path.join(__dirname, 'worker.js'), (err, { arr }) => { if (err) { return null; } arr[0] = 5; }); worker.postMessage({});
把 arr [0] 的值改为5,实际上会在两个线程中修改它。
当然,通过共享内存,我们冒险在一个线程中修改一个值,同时也在另一个线程中进行了修改。但是我们在这个过程中也得到了一个好处:该值不需要进行序列化就可以另一个线程中使用,这极大地提高了效率。只需记住管理数据正确的引用,以便在完成数据处理后对其进行垃圾回收。
共享一个整数数组固然很好,但我们真正感兴趣的是共享对象 —— 这是存储信息的默认方式。不幸的是,没有 SharedObjectBuffer 或类似的东西,但我们可以自己创建一个类似的结构。
transferList参数
transferList 中只能包含 ArrayBuffer 和 MessagePort。一旦它们被传送到另一个线程,就不能再次被传送了;因为内存里的内容已经被移动到了另一个线程。
目前,还不能通过 transferList(可以使用 child_process 模块)来传输网络套接字。
创建通信渠道
线程之间的通信是通过 port 进行的,port 是 MessagePort 类的实例,并启用基于事件的通信。