最近写多线程的时候遇到一个烦恼,就是用起来实在太麻烦,不管是 WebWorker 还是 worker_threads 库,用起来都实在太麻烦了。而且很多时候 IO 密集和 CPU 密集操作很多时候是交织的,有没有一种办法,可以直接在代码中方便的使用多线程呢?
以前我们使用 Worker 要怎么做?现在我们能怎么做?
之前的做法:
// ### 父进程代码 // 比如请求网络数据,IO 操作 const apiData = await request('/api/xxx'); // 为了不阻塞 eventloop 开启子线程,并拿到符合要求的格式 const goodApiData = await new Promise((resolve, reject) => { const worker = new Worker('子进程文件名 xxx.js', { workerData: apiData }); worker.on('message', resolve); worker.on('error', reject); }); // ### 子线程代码 // 这里处理 data 数据,CPU 密集操作 doSomething(workerData) // 再发送回父进程 parentPort.postMessage(data); 代码量这么大,还要写 2 个文件以上文件,数据发送过去再发送回来头都大了!!!费脑!!!
那有没有更好的方法呢?当然使用 ncpu 就能做到!
使用 ncpu 的做法:
import {NCPU} from 'ncpu' // 比如请求网络数据,IO 操作 const apiData = await request('/api/xxx'); // 为了不阻塞 eventloop 开启子线程,并拿到符合要求的格式 const goodApiData = await NCPU.run((data)=>{ // 这里处理 data 数据,CPU 密集操作 doSomething(data) return data; }, [apiData]) //使用数组传参,这有点类似 apply 使用 ncpu 果然爽,一个回调函数就把 CPU 密集型计算搞定了。
爽是爽,但目前有两点强制限制:
- 回调函数不能共用上下文,因为 ncpu 是使用函数复制的方式来实现的,不会保留函数上下文,所以要求函数是强无副作用函数。
- 传入参数都是使用 HTML structured clone algorithm方式来进行克隆的,而非原值。
但正是这两点强制限制,使得线程更加安全了。因为但多个线程同时操作原值,会导致内存数据更新速度赶不上线程更新的速到,导致另一个线程读取数据不正确。而且我们要处理数据时,通常只需要将大循环和递归计算放入线程的回调函数中,所以这两点强制,反而不是坏事。
目前 ncpu 的两个版本
一个是ncpu专门为 node.js 环境设计,另一个是ncpu-web专门为浏览器环境设计。
同时ncpu需要的最低 node.js 版本是 12,而ncpu-web浏览器要求是谷歌浏览器至少 60 以上,火狐 57 以上即可。
在使用的时候要注意这些问题哦!
