Worker: Node.js中的多线程技术和浏览器WebWorkers

JavaScript的多线程技术与传统编程语言多线程技术的区别

  1. 由于语言机制的限制,JavaScript中的线程之间难以共享内存(可以理解为JavaScript中的变量基本存储于线程栈中),这减少线程间的并发同步的问题,保证了JS线程的安全性。
  2. Node.js不支持fork进程,与Unix系统调用fork()不同,child_process模块的fork()函数不会克隆当前的进程,只是单纯地创建一个node实例。
  3. JS线程之间的数据共享基于对象深拷贝技术,无法共享全部对象,比如函数,因此,它们之间通过事件机制传递消息。

Node:使用Worker Threads模块

  • 启动线程,作为一个独立的JavaScript执行线程,必须指定一个入口文件,防止读写其它线程的数据。
const { Worker } = require('worker_threads');
const th = new Worker(__dirname + '/task.js');
th.on('message', data => {
    // handle data
});

启动工作线程时可以传递克隆的对象:

const worker = require('worker_threads');

if (worker.isMainThread) {
    const th = new worker.Worker(__filename, {
        workerData: [{ msg: 'hello', }, { info: 'world'}],
    });
} else {
    console.log(worker.workerData);
}

/**
工作线程接收到了父线程传递的克隆数组
[ { msg: 'hello' }, { info: 'world' } ]
*/
  • 事件
    父子线程之间使用事件传递消息,事件类型如下:
事件名称 描述
message 当子线程调用parentPort.postMessage(data: any)时产生该事件,跨线程接收克隆的data。
exit 当子线程调用parentPort.close()时产生该事件,该事件只会产生一次,后续调用将被忽略。
online 当子线程开始执行时产生该事件。
error 当子线程抛出异常时产生该事件。

不过父进程只能向子线程发送message事件,以及调用terminate()终止子线程。
同时,父子之间可以使用emit(event: string, ...args)模拟对方给自己发送消息,从而主动调用事件处理逻辑。

  • Usage
const child_process = require('child_process');
const worker = require('worker_threads');
const express = require('express');
const colors = require('colors');
const { log, table, error } = console;

if (worker.isMainThread) {
    try {
        main();
    } catch(e) {
        info(e.message);
    }
} else {
    try {
        task();
    } catch(e) {
        info(e.message);
    }
}
return;
// Functions
function info() {
    const list = [];
    [...arguments].forEach(it => {
        list.push(it.toString().rainbow);
    });
    log(...list);
}
function main() {
    const app = new express();
    app.listen(8080);
    app.use((req, res, next) => {
        const th = new worker.Worker(__filename, {
            workerData: {
                msg: '您的抽奖号码为:',
            },
        });
        info('产生工作线程', th.threadId);
        th.once('message', data => {
            info('工作线程', th.threadId, '计算完毕,', '主线程开始回应客户端');
            res.send(data);
        });
    });
}
function task() {
    console.time(worker.threadId);
    info('工作线程', worker.threadId, '开始执行IO或CPU密集任务');
    child_process.execSync('sleep 2');
    worker.parentPort.postMessage([
        { index: worker.threadId, result: worker.workerData.msg + Math.round(Math.random()*100),  },
    ]);
    console.timeEnd(worker.threadId);
}

浏览器

# index.js
(function main() {
    const th = new Worker('./a.js');
    th.onmessage = event => {
        console.log(event.data);
    };
    console.table(th);
})();

# a.js
postMessage({
    msg: 'good',
});

主线程看Worker

worker: Worker
  onerror: null
  onmessage: null
  __proto__: Worker
    onerror: (...)
    onmessage: (...)
    postMessage: ƒ postMessage()
    terminate: ƒ terminate()
    constructor: ƒ Worker()
    Symbol(Symbol.toStringTag): "Worker"
    get onerror: ƒ onerror()
    set onerror: ƒ onerror()
    get onmessage: ƒ onmessage()
    set onmessage: ƒ onmessage()
    __proto__: EventTarget
  __proto__: Object

好明显, 只有四个函数: set onmessage(), set onerror(), postMessage(), terminate().
显然任务的执行应该是不尽相同的,具体由主线程提供参数来决定,postMessage()就起这个作用,当一个Worker脚本执行时,它应该在完成必要的初始化操作后立即进入监听状态,等待主线程的消息,从而触发不同的任务.

工作线程看Worker

工作线程中有三个方式访问worker引用: self, this, 或者像window一样直接访问其worker字段. 不过,建议使用globalThis,这在Node.js中包括woker_thread中通用.
它主要通过set onmessage()postMessage()与主线程通信.

Worker 线程能够访问一个全局函数importScripts()来引入脚本,该函数接受0个或者多个URI作为参数来引入资源;以下例子都是合法的:

importScripts();                        /* 什么都不引入 */
importScripts('foo.js');                /* 只引入 "foo.js" */
importScripts('foo.js', 'bar.js');      /* 引入两个脚本 */

浏览器加载并运行每一个列出的脚本。每个脚本中的全局对象都能够被 worker 使用。如果脚本无法加载,将抛出 NETWORK_ERROR 异常,接下来的代码也无法执行。而之前执行的代码(包括使用 window.setTimeout() 异步执行的代码)依然能够运行。importScripts() 之后的函数声明依然会被保留,因为它们始终会在其他代码之前运行。

脚本的下载顺序不固定,但执行时会按照传入 importScripts() 中的文件名顺序进行。这个过程是同步完成的;直到所有脚本都下载并运行完毕,importScripts() 才会返回。

共享worker

参考

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers

END

posted @ 2020-02-14 22:07  develon  阅读(1240)  评论(1编辑  收藏  举报