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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM