nodejs 后端实现是否需要转同步函数为异步函数,降低主线程的负载
发布于 1 个月前 作者 kanghoulin 1463 次浏览 来自 问答

关于nodejs的几点疑问: 1.底层使用了线程池,应该是使用了多核心cpu吧,只是内存受v8影响只能1.7G? cluster模式只是为了启动更多实例,更多主线程提高并发和资源最大利用? 2.异步函数会加入事件轮询,放入线程池执行,主线程就不会阻塞能处理更多的并发? 3.同步代码会在主线程执行,是否可以通过封装同步代码为异步函数,使代码被线程池内的子线程执行再回调回来,降低主线程压力。 4.通过async/await可以把同步函数修改为异步函数执行。能提高主线程处理能力吗?await性能消耗是否可以忽略?

有大神能帮忙看看吗?nodejs学得不精,感觉好多不清晰的地方,请大家帮忙解惑

40 回复

同步改异步测试demo async function b() { await (() => {})(); let num = 0; for(var i = 0;i < 100000;i ++){ num += new Date().getTime(); } console.log(“ccccc”); console.log(num); return num; }

async function a() { let data = await b(); console.log(data); console.log(“aaaaa”); }

a();

之前测试使用promise 实现异步redis获取转同步,服务端高并发时直接崩溃,是否时promise的资源消耗很高,redis的并发应该不会有问题的。现在不敢在高并发业务场景内使用promise。这个理解正确吗?

  1. demo 代码 b() 不能这么写。本来就是同步阻塞,是无法转变为异步的。除非是丢给 v8 线程池执行(不过那样话接口也自然会是异步的了)
  2. 同步转异步,异步转同步,你想干啥呢……

@waitingsong 1.上面代码测试是可运行,console效果是 先打印出aaaaa,后打印出ccccc。b函数阻塞不会导致a函数阻塞。2.同步如果逻辑太多太复杂是会阻塞主线程,降低主线程的执行能力。如果同步能转为异步不就能使用v8的线程池了吗

你应该理解有误。 就设计上说是先打印 ccc 然后再 打印,否则体现不出 await 的意义。 就实际代码执行结果也是 ccc 先:

ccccc
161223203570777020
161223203570777020
aaaaa

是否阻塞主线程不在于同步异步,而是在于代码是在 js 环境还是 Nodejs 底层的 v8 环境(线程池)执行。

@waitingsong async function b() { await (() => {})(); let num = 0; for(var i = 0;i < 100000;i ++){ num += new Date().getTime(); } console.log(“ccccc”); console.log(num); return num; }

async function a() { //let data = await b(); //这里如果await,就会等待结果同步执行下去 但b函数已是异步函数 // console.log(data); b(); //这里测试不await console.log(“aaaaa”); }

a();

执行结果: aaaaa ccccc 161224681952400960

@zy445566 我试了下ncpu,好像是这个意思。这个是同步函数改成异步执行了吧,能提高主线程的处理能力吧

@zy445566 你有用这个插件吗,有没有什么坑?

重 CPU 的工作,一律丢到 阿里云函数计算(FC)这种地方去。

@kanghoulin 就有一点就是数据是和函数都是使用复制过去的,并不是原有数据。所以函数的上下文不支持,还有就是数据不支持带方法的这种。 数据复制是使用了 https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm 方法复制的

@atian25 很多数据在内存里面,针对有关联性没法独立的逻辑

@zy445566 公用内存就没问题,只要释放的是主线程的压力

@kanghoulin 线程都是共用内存的,不过还是要警惕内存不安全问题,可能多线程读取内存的结果不一致问题。 https://es6.ruanyifeng.com/#docs/arraybuffer#Atomics-对象

@zy445566 我测试,我的全局变量没法公用,确实是复制过去的。可能不好弄

@kanghoulin 这个全局变量不是属于公共内存,这是属于上下文。使用内存可以考虑使用 SharedArrayBuffer 来实现,这个可以实现内存共享

@zy445566 像我上面的代码也是类似的效果,可以使用上下文。使用await (() => {})()让同步函数可以异步(线程池)执行。测试跑业务没问题,开销的话await也不大。这种方法你觉得可行吗? 已做测试环境测试,目前没问题

@zy445566 v8线程池是可以使用多核cpu的吧,只是内存限制还存在,没有突破1.7G的限制是吧

@kanghoulin 我觉得是架构问题,数据可以丢 redis 啥的

@kanghoulin

理论上是不行的,async/await其实还是单线程,你在这里进行大量密集型运算还是会卡住其它运算的。只是说你的业务代码在V8进行了JIT和编译的一些优化后快到你没法察觉而已。

你不信的话可以在你的异步执行这个方法就知道了,传个100s看看

 function stopWorld(s) {
 const exp = Date.now()+s*1000;
 while(Date.now()<exp){}
}

真正多线程是不会因为这个而卡住其它代码的运行

@atian25 我的服务端的设计数据结构比较繁杂,一个json包含全部,如果redis的话每次获取的数据太多了,如果拆分数据又太麻烦了。而且每次redis操作都得json格式化。目前我们是内存操作,定时器保存数据到redis(只做备份)

b();                            //这里测试不await

这个实现(不 await)等同于用 setTimeout() 包裹实现代码。还是会阻塞主进程的,无非是在当前还是在下一个事件循环。

@zy445566

async function stopWorld(s) { await (() => {})(); //实现同步函数转异步效果,不知道是不是真异步(线程池)? const exp = Date.now()+s*1000; while(Date.now()<exp){ } console.log(“stopWorld ok:”,Date.now()); } async function main() { console.log(“start:”,Date.now()); //await stopWorld(5); //加await,main函数会等待stopWorld函数执行完成返回结果 stopWorld(5); //不等待stopWorld函数返回结果 console.log(“end:”,Date.now()); }

main();

执行结果: start: 1612255375218 end: 1612255375226 stopWorld ok: 1612255380227

@waitingsong 哦哦,我以为是不会阻塞,我看打印消息结果 后面的逻辑已经执行了

@waitingsong 我说“阻塞“是不是不正确,我想实现的结果应该是b函数进入线程池执行,主函数继续执行。线程池里面执行应该就不会消耗主线程的处理能力吧。不知道理解是否正确

@kanghoulin JS 本身是单进程模式,需要本地运行的代码作都是在(这个)进程中执行,必然会阻塞(这个)进程。 而 Node.js 提供了 v8 运行环境(C++)实现,有个线程池来运行代码。 我们写的 JS 代码通常都是在 JS 的(单)进程中执行,有些代码可能会被优化到底层 v8 的环境执行。

@waitingsong 意思是 异步函数的执行 也是主进程中执行,不一定放入线程池是吗?

@kanghoulin 异步函数,如果是访问外设与network,并且调用的是 nodejs 提供的接口,那就是在 v8 的线程池中执行。

@kanghoulin 这么说吧,stopWorld,你发放到http请求里就知道了,只要他运行其它请求统统进不来。还有node的线程池主要是针对FS这个模块,你一般的代码一般是没有线程池优化的

@zy445566 这个是异步阻塞。 异步 != 不阻塞 哈哈

@waitingsong 哦哦,这个还真不知道,我一直理解是异步方法会被事件机制放入线程池执行

@waitingsong 这个我理解确实有问题,我一直理解是异步方法会被事件机制放入线程池执行,这样就不会阻塞到主线程执行

@waitingsong 我不白话点,不让他去实践,他不明白的

@kanghoulin 同步对应异步, 阻塞对应不阻塞,这个两个不同的概念,不能等同说异步就是不阻塞。 我的理解:

  1. 同步、异步是执行方法上面区别,但是执行的过程就产生了阻塞与非阻塞的概念。
  2. 同步就会阻塞。 异步多数情况是非阻塞,但是也会有阻塞,取决于调用方法的实现。

@waitingsong 理解了

异步函数,如果是访问外设与network,并且调用的是 nodejs 提供的接口,那就是在 v8 的线程池中执行。

nodejs解决阻塞的办法就只能自己开子进程才能解决了是吧,代码实现脱离当前的js线程。

谢谢大家,异步和阻塞 之前确实没有弄明白

@kanghoulin 常用方式:

  1. fork、spawn 子进程进行处理
  2. worker ,权限有限制,不能访问 DOM https://developer.mozilla.org/zh-CN/docs/Web/API/Worker
  3. 写 nodejs 的扩展(运行在 V8 线程池)

方法就实现角度来说,只要是同步不管怎么样都是会阻塞主线程的,比如带sync字眼的函数,把sync方法封装也是无用的,同步就是同步,async和await只是异步方法回调的另一种语法,使用它,首先这个方法就是异步的,异步的意思就是“先执行,后查询结果”,不管什么语言都是一个道理,而这个方法怎么异步执行的,对于你这个线程的代码来说不需要理会,其背后的实现各有各的方式。在js中,想让同步方法不影响主线程,主要的思想就是“让其他进程运行这个方法”,这样就打个比方委托其他程序比如你用其他语言或者另一个js进程实现的计算服务(像云计算),这个进程运行玩了“通知”你的代码。所以最后,node提供了worker_thread,看看文档就可以自己写一个简单的“多线程”js,把你的同步代码在另一个node进程中执行,而通过共享buffer交换数据,可以尝试。

回到顶部