关于 worker_threads 执行顺序

2020-11-10 00:13:13 +08:00
 hgjian
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
平台
3200 次点击
所在节点    Node.js
20 条回复
yyfearth
2020-11-10 02:46:38 +08:00
我记得好像 console.log 有 buffer
mingl0280
2020-11-10 06:51:42 +08:00
没人告诉过你线程的执行顺序看操作系统当时状态决定么?!谁告诉你线程执行序有保证的?!
hgjian
2020-11-10 09:10:50 +08:00
@mingl0280 我是觉得,应该是

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

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

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

当然这只是我的猜测

你可以不用 console.log 用别的方法试试 比如直接输出 stdout 并且 flush
misdake
2020-11-10 09:51:35 +08:00
我觉得可能的一种 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
2020-11-10 09:51:55 +08:00
@yyfearth 感谢解答
hgjian
2020-11-10 09:53:12 +08:00
@misdake 谢谢解答
hgjian
2020-11-10 10:21:41 +08:00
@misdake
如果在 isMainThread 判断之前加上一句 console.log ,提前刷一下 console.log 的 buffer,输出的顺序应该就和你想象的一样了。

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


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

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

你可以判断同一线程内的执行顺序,比如 123 就是 123,不可能是 132,456 也一定是 456,不可能是 465 。
mingl0280
2020-11-10 12:04:21 +08:00
@hgjian 线程 1 你 post 完 message 以后就是两个线程并行执行的……worker.on 又没说哪里来的 message (我估计这个 post 直接给两个函数都发送了信号)
hgjian
2020-11-10 13:39:11 +08:00
@des 谢谢
hgjian
2020-11-10 13:39:51 +08:00
@libook 谢谢你的详细说明
hgjian
2020-11-10 13:49:14 +08:00
@mingl0280 谢谢说明
zy445566
2020-11-10 17:48:27 +08:00
用[ncpu]( https://github.com/zy445566/ncpu)使用 await 可以保证顺序,但是如果要保证性能最大肯定也是 Promise.all 来 ncpu 的任务。也可以使用同一个 ncpuWorker 来保证执行顺序
zy445566
2020-11-10 17:50:26 +08:00
上面链接多打了个空格导致 markdown 没识别,正确地址是: https://github.com/zy445566/ncpu
zy445566
2020-11-10 18:04:30 +08:00
其实线程安全主要是集中在 CPU cache 的问题,因为 CPU cache 速度比内存要快,所以会从内存中复制一份数据到 CPU cache 中,运算完成之后再复制回内存。
所以如果是单线程这样是没问题的,但是如果是多线程,就会出现一个时间差,比如两个线程先后复制到 CPU cache 中,但是计算完成后,再先后复制回内存。而这个时间差就会导致,一个线程计算的值不是另一个线程计算完成后的值,导致的线程不安全。
但 JS 也有《 SharedArrayBuffer API 提供 Atomics 对象,保证所有共享内存的操作都是“原子性”的。》--这句话引用自阮一峰的 ECMAScript 6 入门
hgjian
2020-11-10 21:50:14 +08:00
@zy445566 谢谢你的解释,我再看看你的 github 项目
no1xsyzy
2020-11-11 10:22:18 +08:00
@mingl0280 worker.postMessage 给两个函数发信号的话 456 应该被打印两遍……
这里其实相当于用 token 传递的方式进行了保序,那肯定是 console 带 buffer 的问题了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/723408

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX