zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    The Google File System 中文版论文(上)(转载)
    百度技术沙龙(第1期) 2. 豆瓣数据存储实践(转载)
    YunTable开发日记(1) 计划 (转载)
    对SQL说不!NoSQL的数据库技术革命(转载)
    YunTable开发日记(8)聊聊分布式数据库的作用(转载)
    探索Google App Engine背后的奥秘(4) Google App Engine的架构(转载)
    企业中的NoSQL(转载)
    探寻关系数据库和ORM的最佳替代者(转载)
    探索Google App Engine背后的奥秘(3) Google App Engine的简介(转载)
    YunTable开发日记(2) – 前三天的总结 (转载)
  • 原文地址:https://www.cnblogs.com/develon/p/12309862.html
Copyright © 2011-2022 走看看