Node.js能否通过异步解决10s的while?
发布于 6 年前 作者 Jianzs 7055 次浏览 来自 问答

Node.js是单线程的,异步也只是交换了执行的次序,也就是,所有的步骤运行的总时间不会改变,那么总会执行到需要很长时间的地方,此时,Node.js的响应就会停止。这是否是正确的。

Untitled.png 在这个函数里,我在setTimeout里的while强制让它运行10s,这样,虽然,我上传文件的网页虽然立即响应,得到Received,但是,接下来的10s,我在访问其他网页Index等,都不能响应,这样问题在如何解决。

刚开始Node.js的小白,请前辈指教,谢谢。

33 回复

有解决这种问题的方法么?比如我上传了一个很大的文件,写入的时候会花很长时间,这个我问题中的while是一类问题么?

fs.renameSync 之类同步操作会阻塞js进程的

@waitingsong 把重命名操作注释掉,同样会如此,因为,程序使卡在了while,这时,不能在进行别的运算,也就不能处理新的访问。不过,实际情况renameSync时间很长,就相当于这里的while,阻塞了js,这时应该怎么处理?谢谢。

开另一个node进程来处理这样的操作

@dislido 小白,不是很懂,怎么开多个node进程?

异步是不是只针对I/O而言的?也就是,异步只是把这个线程的控制权暂时给了下一个语句,一旦线程有一点空闲,就会处理以前积攒下的东西。也就是说,node一直在处理一些很细小的东西,不能处理连续的东西,如果处理连续的东西,比如while,就会卡壳?但是如果处理很小的一个个事件,每一个小事件仅花很少的时间,这样cpu就可以利用空闲把所有小的事件进行处理,不一定那个先哪个后,总之处理完后就回调,回调到最后发送数据,就完成了一整个访问。 这样说是正确的么?

@Jianzs 不太懂为什么会写 while 强制 10s 呢?在 js 这是完全阻塞进程的,这段代码并没有实际价值

@leavesdrift 对,实际肯定不会有这种while,但是,会不会有死循环while,或者写入硬盘或者renameSync,这些操作耗时很长,这时候产生的效果,是不是就和这个强制10s的while是一样的了。?

@Jianzs 如果是同步执行或者大量计算,你理解的是没错的。多进程是一个解决方案,分割计算也可以。

来自酷炫的 CNodeMD

为何不用异步 io ?为什么要用 xxxSync

From PWA CNode

@Jianzs 对,这种同步操作在任何语言中都是。

@liuqipeng417 终于得到了肯定的答案,感激。用node可以解决么?因为node使单线程,所以不能解决,是么?

@Lizhooh 实际生产环境中,会不会有很大的连续计算的情况?我就是怕有这中情况发生。

@leavesdrift 好吧,谢谢,有木有方法解决呢?多线程?必须换另一种后端么?

@Jianzs 可以新开一个进程,将所有耗时的 cpu密集计算 之类的任务交给新开的子进程处理

@leavesdrift 我没做过这种操作,但是并不需要换,你可以详细看一下 child_process 的 api,比如按照我的理解你可以将 cpu 密集计算单独写成一个脚本,然后在主进程中通过 execFile 去执行这个脚本,然后通过 process.send() 将结果发回给主进程。主进程通过 监听 message 事件拿到这个结果。最好是使用 spawn 这种相对更为底层的 api 因为 execFile 都是在它的基础上包装了以下而已,并且 exec 是由 200KB 的最大 输出缓存的。

当然我觉的应该有很多关于此的实现,cluster,child_process 都很底层你可以看一下,如果已经足够你的需求那么可以自己写,如果不够或者觉得难以处理可以找以下现有的实现。

我并没有实际写过 cpu密集计算 之类耗时的东西,所以如果你有成功后的感想也可以分享下。

并且 node 的计算速度很快,基本都能力压 pypy,与 go 有一些差距但是相对于快速的开发速度完全可以接受。

@leavesdrift 哦~~可以可以,谢谢前辈,受教啦。

@Jianzs 我不是前辈额。学习的小白。

@leavesdrift 呃呃,比我懂得多的,自然是前辈,“其闻道也固先乎吾, 吾从而师之”,哈哈。我才刚开始接触node,感觉好多很迷,很感激有你们这些前辈能传道解惑,谢谢!!!

@Jianzs 没事没事,一起学习!

@Jianzs 去看一下 Nodejs 的事件循环和异步 IO 机制,会比较好。

计算型的应用就不要用node了,计算型的一般需要开多线程,有一个或多个线程专门用户接收用户请求,一组worker线程来处理耗时任务。 node的多线程模型不好,共享内存都难以办到,开发起来很不方便的,建议选用其他语言。

大文件上传 会考虑到失败重传,另外在服务端需要注意文件在内存的临时缓存大小(占用太多,内存就GG了),较好的方案是分割成一个个小块,一块块的传。如果那个while代表写入文件的话,建议换成管道操作。

@yyrdl @Lizhooh 谨受教,谢谢两位前辈。

。。。。。为什么要用同步方法+1 用异步方法然后把后续代码丢到回调里就好了啊。。

如果你这个10s是指io操作的耗时,使用异步io就不会阻塞。node单线程是指脚本运行是单线程的,而io操作并不在这个线程中执行。

把这个function的前面加上async不就行了吗……

丢到异步里,然后pub/sub来做

还以为是……

把while替换为 setTimeout{ response.write }

webworker threads,这个可在nodejs里开线程

来自酷炫的 CNodeMD

耗时和死循环是2会事。如果是单核的话,确实是回有卡死的情况,比如CPU满负荷。下个GDAL的图形库,很容易把程序跑假死,一般CPU满符合,在对service进行请求,servce是没有任何响应的。等到CPU降下去后,会处理之前没有处理的请求,我觉得你这个whil可能就是这个原因

感觉没人说到点子上,都是什么多开node换语言的,甚至还有说fs阻塞了(是阻塞了,但不至于全部卡10秒),什么JB。 至于作者说fs写文件会卡死大可不必担心,fs这边node可没说自己是单线程(不然node吹的适用io密集不是白吹的),有兴趣可以看看源代码,再不济自己可以多做测试也能知道十之八九。 一句话就是要去除死循环10秒的while,所以这种长时间死循环的while必删!低级点你用setTimeout的10s包住respones的write和end,高级点用yield或await暂停10s 你现在走的,就是我下面代码中阻塞10s的例子!(顺便说一下贴代码下次别截图了,用markdown的代码标记包裹,不然想帮你改都麻烦)

const co =require('co');
function stopTimeWhile(s)
{
    let m = s*1000;
    let nowTime = new Date();
    while (new Date()-nowTime<m);
    return true;
}

function stopTimePromise(s)
{
    let m = s*1000;
    return new Promise((res,rej)=>{
        setTimeout(()=>{
            res(true);
        },m)
    });
}

//以下暂停不会阻塞node
(async function(){
    await stopTimePromise(10);
    console.log("await10")
})();

co(function *(){
   yield stopTimePromise(10);
   console.log("yield10")
});

//以下会阻塞node
(function(){
    stopTimeWhile(10);//此时还未进入node队列,就阻塞了,故while10先输出
	//(就算在队列中也会阻塞队列的下一条执行,并不是说进入了队列就不存在阻塞,怕大家会意错我上一条注释的意思)
    console.log("while10")
})();

楼主被单线程三个字坑了,所谓单线程是说js是在主进程里执行,但是很多异步操作,是要交给osthread pool

node把异步操作封装到libuv中,比如http模块的异步交给了osepollfs模块的异步交给了thread pool, libuv还屏蔽了操作系统差异。

这个thread pool与执行js的主进程不同,你可以理解成另外的worker thread,默认启动数是4个(官方文档都有的),形成一个执行队列

可以看看这个回答https://stackoverflow.com/questions/22644328/when-is-the-thread-pool-used

回到你的问题,while卡住了js主进程,所以后续请求处理不了。就是活不用你干,但你也要给我机会接活和分活~ 如果你能异步的尽量异步了,也就是把负载从node分发到os了,那时候可能就是os的瓶颈而不是node了

@yyrdl 同意,推荐使用node-java使用JAVA实现计算密集型的任务,然后,从JS里直接调用。步骤大约是:

  1. 使用JAVA做一个完成计算密集型工作的JAR文件
  2. npm install java
  3. 然后,在程序内
const java = require('java');
java.classpaths.push('jar文件路径');
const className = java.import('JAVA类名');
const instance = new className(); // 创建这个JAVA类的一个实例
instance.methodName(参数列表, (err, result) => { // 调用成员方法,注意:这是异步的。
   // 处理result
});

不需要像Android WebView那样注册Bridge API。直接导入JAVA Class和调用成员函数。

回到顶部