V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hgjian
V2EX  ›  Node.js

关于 worker_threads 执行顺序

  •  
  •   hgjian · 2020-11-10 00:13:13 +08:00 via Android · 3180 次点击
    这是一个创建于 1465 天前的主题,其中的信息可能已经有所发展或是发生改变。
    const { Worker, isMainThread, threadId , parentPort } = require("worker_threads");

    function a(){
    var worker = new Worker( __filename , { stdout : false , stderr : false } ) ;

    worker.postMessage( { } )

    worker.on( "message" , msg => {
    console.log( ' 4 ' ) ;
    console.log( ' 5 ' ) ;
    console.log( ' 6 ' ) ;
    } ) ;
    }

    function b(){

    parentPort.on( "message" , msg => {

    console.log( ' 1 ' ) ;
    console.log( ' 2 ' ) ;
    console.log( ' 3 ' ) ;

    parentPort.postMessage( { } ) ;

    } ) ;
    }

    if (isMainThread) {
    a()
    }else{
    b()
    }

    请问打印结果为什么是
    1
    4
    5
    6
    2
    3
    感觉应该是
    1
    2
    3
    4
    5
    6
    才对啊。
    win10,
    nodr.js14
    平台
    20 条回复    2020-11-11 10:22:18 +08:00
    yyfearth
        1
    yyfearth  
       2020-11-10 02:46:38 +08:00
    我记得好像 console.log 有 buffer
    mingl0280
        2
    mingl0280  
       2020-11-10 06:51:42 +08:00
    没人告诉过你线程的执行顺序看操作系统当时状态决定么?!谁告诉你线程执行序有保证的?!
    hgjian
        3
    hgjian  
    OP
       2020-11-10 09:10:50 +08:00
    @mingl0280 我是觉得,应该是

    顺序应该是 a 函数的 worker.postMessage( { } ) 触发 b 函数 执行 parentPort.postMessage( { } ) ;
    执行完 b 的 parentPort.postMessage( { } ) ; 才会触发 主线程的 worker.on 函数;

    请教一下,难道不是这样吗?
    hgjian
        4
    hgjian  
    OP
       2020-11-10 09:11:14 +08:00
    @yyfearth console.log 有 buffer,是什么意思啊?
    yyfearth
        5
    yyfearth  
       2020-11-10 09:39:47 +08:00   ❤️ 1
    我记得 console.log 有 io 的 buffer
    所以可能 console.log 输出的时候比实际晚

    依旧是说 你 call parentPort.postMessage( { } ) 的时候 console 还在 buffer 里面
    然后不同的 worker buffer 不共享
    所以两者的输出穿插了

    当然这只是我的猜测

    你可以不用 console.log 用别的方法试试 比如直接输出 stdout 并且 flush
    misdake
        6
    misdake  
       2020-11-10 09:51:35 +08:00   ❤️ 1
    我觉得可能的一种 145623 的运行顺序:(不保证正确)
    第一个 console.log(1)的时候,发起了刷 buffer 的请求,输出 1 。接下来的 bcde 因为没赶上这次的输出,被压进了主线程的 buffer,等待下一次刷新。
    worker 线程在跑的时候,1“正在”输出,刷新时 worker 线程的 buffer 里已经有 456 了,输出 456 。(猜测不同线程的刷新是轮询的,而不是优先盯着一个)
    下次刷主线程 buffer 的时候,buffer 里有 23,输出 23 。


    如果在 isMainThread 判断之前加上一句 console.log ,提前刷一下 console.log 的 buffer,输出的顺序应该就和你想象的一样了。感觉就是 buffer 相关的原因。
    hgjian
        7
    hgjian  
    OP
       2020-11-10 09:51:55 +08:00
    @yyfearth 感谢解答
    hgjian
        8
    hgjian  
    OP
       2020-11-10 09:53:12 +08:00
    @misdake 谢谢解答
    hgjian
        9
    hgjian  
    OP
       2020-11-10 10:21:41 +08:00
    @misdake
    如果在 isMainThread 判断之前加上一句 console.log ,提前刷一下 console.log 的 buffer,输出的顺序应该就和你想象的一样了。

    ===>> 在好几个地方 提前加了 console.log ,输出还是乱的,没有效果,我再研究看看有没有别的办法
    des
        10
    des  
       2020-11-10 11:21:38 +08:00   ❤️ 1
    写是有 buffer 的,想要得到你预期的结果
    请使用 fs.writeFileSync(0, 'stdout: ' + msg + '\n');
    libook
        11
    libook  
       2020-11-10 11:30:16 +08:00   ❤️ 1
    举个例子吧:
    你手里有 1 元、2 元、5 元的三张纸币,我手里有 10 元、20 元、100 元三张纸币,你走到一个存钱罐前,喊我过去和你一起把各自手里的纸币按照各自的从小到大顺序塞进存钱罐,但是存钱罐每次只能塞一张纸币,所以咱俩只要没有人堵着投币口就可以往里面塞,因为没有对双方塞入的顺序进行控制,所以一开始可能是我塞入第一张也可能是你塞入第一张,而且有可能是一个人塞得快连续塞入两张纸币第另个人才有机会塞入一张,也可能一个人在塞入某一张的时候卡住了需要更多时间调整姿态,另一个人得等着……总之,你就是没法确定你是第一个塞入 1 元的,我也没法确定我啥时候能塞入第一账;但是我们都能确定,你自己塞入的顺序一定是 1 元、2 元、5 元,我自己塞入的顺序一定是 10 元、20 元、100 元。


    引入多线程之后,就会有一个问题叫做“线程安全”,因为多个线程是同时运行的,所以在你没有特别控制执行顺序或确保最终一致性的措施的情况下,两个线程的执行顺序是不确定的,取决于你的操作系统和硬件当时是如何调度的。

    也就是说,只要你开辟了新的线程,就不要尝试去判断新线程里的程序啥时候执行。

    你可以判断同一线程内的执行顺序,比如 123 就是 123,不可能是 132,456 也一定是 456,不可能是 465 。
    mingl0280
        12
    mingl0280  
       2020-11-10 12:04:21 +08:00   ❤️ 1
    @hgjian 线程 1 你 post 完 message 以后就是两个线程并行执行的……worker.on 又没说哪里来的 message (我估计这个 post 直接给两个函数都发送了信号)
    hgjian
        13
    hgjian  
    OP
       2020-11-10 13:39:11 +08:00
    @des 谢谢
    hgjian
        14
    hgjian  
    OP
       2020-11-10 13:39:51 +08:00
    @libook 谢谢你的详细说明
    hgjian
        15
    hgjian  
    OP
       2020-11-10 13:49:14 +08:00
    @mingl0280 谢谢说明
    zy445566
        16
    zy445566  
       2020-11-10 17:48:27 +08:00
    用[ncpu]( https://github.com/zy445566/ncpu)使用 await 可以保证顺序,但是如果要保证性能最大肯定也是 Promise.all 来 ncpu 的任务。也可以使用同一个 ncpuWorker 来保证执行顺序
    zy445566
        17
    zy445566  
       2020-11-10 17:50:26 +08:00
    上面链接多打了个空格导致 markdown 没识别,正确地址是: https://github.com/zy445566/ncpu
    zy445566
        18
    zy445566  
       2020-11-10 18:04:30 +08:00
    其实线程安全主要是集中在 CPU cache 的问题,因为 CPU cache 速度比内存要快,所以会从内存中复制一份数据到 CPU cache 中,运算完成之后再复制回内存。
    所以如果是单线程这样是没问题的,但是如果是多线程,就会出现一个时间差,比如两个线程先后复制到 CPU cache 中,但是计算完成后,再先后复制回内存。而这个时间差就会导致,一个线程计算的值不是另一个线程计算完成后的值,导致的线程不安全。
    但 JS 也有《 SharedArrayBuffer API 提供 Atomics 对象,保证所有共享内存的操作都是“原子性”的。》--这句话引用自阮一峰的 ECMAScript 6 入门
    hgjian
        19
    hgjian  
    OP
       2020-11-10 21:50:14 +08:00 via Android
    @zy445566 谢谢你的解释,我再看看你的 github 项目
    no1xsyzy
        20
    no1xsyzy  
       2020-11-11 10:22:18 +08:00
    @mingl0280 worker.postMessage 给两个函数发信号的话 456 应该被打印两遍……
    这里其实相当于用 token 传递的方式进行了保序,那肯定是 console 带 buffer 的问题了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2665 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:46 · PVG 23:46 · LAX 07:46 · JFK 10:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.