求不更学不动之Node.js多线程

简介: Node.js由于JS的执行在单一线程,导致CPU密集计算的任务可能会使主线程会处于繁忙的状态,进而影响服务的性能,虽然可以通过child_process模块创建子进程的方式来解决,但是一方面进程之间无法共享内存,另一方面创建进程的开销也不小。

伴随10.5.0的发布,Node.js 新增了对多线程的实验性支持(worker_threads模块)。

为什么需要多线程?

Node.js由于JS的执行在单一线程,导致CPU密集计算的任务可能会使主线程会处于繁忙的状态,进而影响服务的性能,虽然可以通过child_process模块创建子进程的方式来解决,但是一方面进程之间无法共享内存,另一方面创建进程的开销也不小。所以在10.5.0版本中Node.js提供了worker_threads模块来支持多线程,一直以来被人所诟病的不擅长CPU密集计算有望成为历史。

如何启用多线程?

多线程目前仍然处于实验阶段,所以启动时需要增加--experimental-workerflag才能生效。

如何创建多线程?

worker_threads模块中比较重要的几个类:

MessageChannel: 用于创建异步、双向通信的通道实例。MessageChannel实例包含两个属性port1和port2,这两个属性都是MessagePort的实例。

MessagePort: 用于表示MessageChannel通道的终端,用于Worker之间传输结构化数据、内存区域和其他的MessagePort。MessagePort继承了EventEmitter,因此可以使用postMessage和on方法实现消息的传递与接收。

Worker: 用于创建单独的JS线程。

worker_threads模块中比较重要的几个属性:

parentPort: 子线程中的parentPort指向可以与主线程进行通信的MessagePort。

子线程向父线程发送消息


parentPort.postMessage(...)

子线程接受来自父线程的消息


parentPort.on('message', (msg) => ...)

isMainThread: 用于区分当前文件是否在主线程中执行

workerData: 用于传递给Worker构造函数的data副本,在子线程中可以通过workerData获取到父进程传入的数据。

了解常用类与属性之后再来看一下代码示例


const { Worker, parentPort, isMainThread } = require('worker_threads');
if (isMainThread) {
  const w = new Worker(__filename, {
    workerData: {
      name: 'Randal'
    }
  });
  w.postMessage(1e10);
  const startTime = Date.now();
  w.on('message', function(msg) {
    console.log('main thread get message: ' + msg);
    console.log('compute time ellapsed: ' + (Date.now() - startTime) / 1000);
  });
  console.log('main thread executing');
} else {
  const longComputation = (val) => {
    let sum = 0;
    for (let i = 0; i < val; i++) {
      sum += i;
    };
    return sum;
  };
  parentPort.on('message', (msg) => {
    console.log(`${workerData.name} worker get message: ` + msg);
    parentPort.postMessage(longComputation(msg));
  });
}

// 执行结果
main thread executing
Randal worker get message: 10000000000
main thread get message: 49999999990067860000
compute time ellapsed: 14.954

线程间如何传输数据?


port.postMessag(value[, transferList])

除了value之外,postMessage方法还支持传入transferList参数,transferList是一个List,支持的数据类型包括ArrayBuffer和MessagePort对象,transferList中的对象在传输完成后,在发送对象的线程中就不可以继续使用了。

const { Worker, isMainThread, parentPort } = require('worker_threads');
// 主线程
if (isMainThread) {
  const sab = new ArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 100);
  const ia = new Int32Array(sab);

  for (let i = 0; i < ia.length; i++) {
    ia[i] = i;
  }
  console.log("this is the main thread");
  for (let i = 0; i < 1; i++) {
    let w = new Worker(__filename);
    console.log('before transfer: ', sab);
    w.postMessage(null, [
      sab
    ]);
    setTimeout(() => {
      console.log('after transfer: ', sab);
    }, 1000);
  }
} else {
  console.log("this isn't main thread");
}
// 输出结果
this is the main thread
before transfer:  ArrayBuffer { byteLength: 400 }
this isn't main thread
after transfer:  ArrayBuffer { byteLength: 0 }

如果ArrayBuffer是通过value传输的(且在transferList中不存在),则传输过去的是副本,如下所示:

w.postMessage(sab);

// 输出结果
this is the main thread
before transfer:  ArrayBuffer { byteLength: 400 }
this isn't main thread
after transfer:  ArrayBuffer { byteLength: 400 }

线程间如何共享内存?

轮到SharedArrayBuffer出场了,如果postMessage中的value是SharedArrayBuffer的话,则线程之间就可以共享内存,如下面例子所示:


const { Worker, isMainThread, parentPort } = require('worker_threads');
// 主线程
if (isMainThread) {
  const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 5);
  const ia = new Int32Array(sab);

  for (let i = 0; i < ia.length; i++) {
    ia[i] = i;
  }
  for (let i = 0; i < 2; i++) {
    let w = new Worker(__filename);
    w.postMessage(sab);
   w.on('message', () => {
    console.log(ia);
   });
  }
} else {
  parentPort.on('message', (msg) => {
    const ia = new Int32Array(msg, 0, 1);
    ia[0] = ia[0] + 1;
    parentPort.postMessage('done');
  });
}

// 输出结果
Int32Array [ 1, 1, 2, 3, 4 ]
Int32Array [ 2, 1, 2, 3, 4 ]

参考资料

medium.com/dailyjs/th.…
nodejs.org/dist/lates.…


原文发布时间为:2018年06月27日
作者:掘金
本文来源:  掘金  如需转载请联系原作者
相关文章
|
1月前
|
数据采集 并行计算 JavaScript
实战指南:在 Node.js 中利用多线程提升性能
在 Node.js 的世界中,多线程技术一直是一个受到广泛关注的领域。最初,Node.js 设计为单线程模式。随着技术发展,Node.js 引入了多线程支持,进而利用多核处理器的强大性能,提升了应用性能。接下来的内容将深入探讨 Node.js 如何实现多线程,以及在何种场合应该采用这种技术。
|
1月前
|
前端开发 JavaScript UED
由于JavaScript是单线程的,因此在处理大量异步操作时,需要确保不会阻塞UI线程
【5月更文挑战第13天】JavaScript中的Promise和async/await常用于处理游戏开发中的异步操作,如加载资源、网络请求和动画帧更新。Promise表示异步操作的结果,通过.then()和.catch()处理回调。async/await作为Promise的语法糖,使异步代码更简洁,类似同步代码。在游戏循环中,使用async/await可清晰管理资源加载和更新,但需注意避免阻塞UI线程,并妥善处理加载顺序、错误和资源管理,以保证游戏性能和稳定性。
31 3
|
1月前
|
JavaScript 前端开发
JS 单线程还是多线程,如何显示异步操作
JS 单线程还是多线程,如何显示异步操作
30 2
|
1月前
|
消息中间件 JavaScript 前端开发
Node.js 中的线程 与 并发
Node.js 中的线程 与 并发
25 0
|
1月前
|
消息中间件 前端开发 JavaScript
JavaScript 线程:处理高并发任务的必备知识(下)
JavaScript 线程:处理高并发任务的必备知识(下)
JavaScript 线程:处理高并发任务的必备知识(下)
|
1月前
|
前端开发 JavaScript UED
JavaScript 线程:处理高并发任务的必备知识(上)
JavaScript 线程:处理高并发任务的必备知识(上)
JavaScript 线程:处理高并发任务的必备知识(上)
|
1月前
|
前端开发 JavaScript
异步编程:由于JS是单线程执行的,所以对于耗时的操作(如网络请求),需要通过异步编程来处理。回调函数、Promise、async/await都是常用的异步编程方式。
异步编程:由于JS是单线程执行的,所以对于耗时的操作(如网络请求),需要通过异步编程来处理。回调函数、Promise、async/await都是常用的异步编程方式。
56 1
|
11月前
|
JavaScript 前端开发
js单线程、同步、异步
什么是单线程?同步、异步的产生?
93 0
|
JavaScript 前端开发 Shell
NodeJS 多线程发展史
在开始了解多线程之前先来了解一下程序任务按照性能的分类,从性能方面来看,计算机程序主要分为两个维度,CPU密集型(也叫计算密集型)和UO密集型。
250 0
|
JavaScript
Arcgis js多线程克里金插值初体验
最近做关于雨量插值的项目,本来使用后台的GP工具做的,但是处理时间比较长需要十几秒钟左右,所以研究怎么通过前台来计算。
98 0

热门文章

最新文章