NodeJs异步式疑问
发布于 11 年前 作者 xiuxu123 7595 次浏览 最后一次编辑是 8 年前

在看Node.js开发指南的时候看到下面这段话:

看看Node.js是如何解决这个问题的: db.query(‘SELECT * from some_table’, function(res) { res.output(); }); 这段代码中 db.query 的第二个参数是一个函数,我们称为回调函数。进程在执行到 db.query 的时候,不会等待结果返回,而是直接继续执行后面的语句,直到进入事件循环。 当数据库查询结果返回时,会将事件发送到事件队列,等到线程进入事件循环以后,才会调 用之前的回调函数继续执行后面的逻辑。

但是看不懂这里说的:
进程在执行到db.query 的时候,不会等待结果返回,而是直接继续执行后面的语句,直到进入事件循环。 当数据库查询结果返回时,会将事件发送到事件队列,等到线程进入事件循环以后,才会调 用之前的回调函数继续执行后面的逻辑。

我个人的理解是: 所有的请求都是异步的,然后当所有的请求到达服务器的时候,对磁盘的I/O操作不会等待结果返回,而是直接执行回调函数,并不是直接返回给客户端,而是这时进入一个事件队列等待处理,而cpu和内存在同一时间只处理一件事,这个时候可能I/O操作已经得到返回值了,然后把返回结果也发送到刚才已经排在队列里面的回调函数,等到cpu处理到这里的时候它们已经有结果了,cpu就直接处理此事件, 打个比方: 客户端的请求就好比两个人A,B去买东西,A就是I/O,B就是回调函数,到达超市的时候A去货架上面选东西,而B这个时候并不和A一起去,而是一个人说我先去排队付款,你好了来找我我先占个位置,B就去排队了,而收银员在同一时间是只收取一个人的费用的,当A选完东西之后它会去找排队的B,一起排队等待收银员收取费用(也就是等待处理),收银员收取费用之后A,B就回家了,也就想当于处理了一次完整的请求返回给客户端,

不知道我的理解对不对?敬请各位大神指点下,

3 回复

我是这样理解的: Nodejs Server 可以理解成对一个个请求的响应方法和一个队列. 对于不需要异步的请求,方法会直接返回这样一次请求就会结束, 如果需要IO, 数据库等比较耗时的操作就用异步的方式把他们放到队列里, 而响应请求的方法会立刻结束而不会阻塞其他的响应方法,从而加快服务器响应. 而在队里中的工作执行完成之后会返回给用户. 场景是这样的: 同时有两个请求A, B. A需要1000ms异步操作, B不需要异步. 假如A比B早1ms. 对于单线程的Node如果不用异步的方式, B需要在10001ms的时候才能拿到响应. 但是因为Node是异步的, 所以A请求到来时,需要花费1000ms的读取操作被放到了队列, B请求可以立刻得道处理,并返回结果. 等处理队列完成之后A用户会得道返回内容, A用户的等待时间还是1000ms. 这也是为什么Node可以采用单线程的原因. 对于A来说他还是需要等待1000ms, 但是A请求通过异步的方式不过影响其他请求. 在其他语言中这是通过多线程实现的. 但是在大量并发请求发生的时候, Nodejs的优势就会体现出来.

我想请问下的就是,如果B请求还有C、D、E、F…等,总共花费时间加起来大于A所需要的1000ms,这个时候A结果也已经返回回来了,进入队列等待了吧,此时是先将B后面的C、D、E、F…等执行完过后再执行回调函数呢还是,比如把D执行完了刚好超过了1000ms,此时就直接去队列执行A回调函数呢?也就是里面说的事件循环究竟是如何运行的?谢谢!

@pf12345,可以从以下一些方面来看

事件循环解析

  1. 所有的javascript引擎都有一个事件队列的概念(也就是事件循环的数据结构)
  2. 事件是异步的基础,异步操作的结果最终表现为一个事件的发生,但是异步操作本身并不是事件, 典型的事件比如IO, Timer, …
  3. 事件的发生是异步的,在什么时候发生是不可预料的,不管主线程是否正在进行任务处理,事件可随时出现
  4. 当事件发生时,针对事件的回调例程(可以理解为回调函数,虽然不仅仅是回调)会被做为一个例程实例放入事件队列。请注意,例程实例是在事件已经发生了才添加进事件队列的,而不是在刚执行异步操作时就放入, 还要注意的就是,例程实例的放入是异步于主线程的,不管主线程是否在忙都会放入,而不会被主线程阻塞
  5. 主线程不断的忙碌着,只要事件队列不为空,则取出事件队例的第一个实例,执行之。。。

三个问题

以上就是事件循环的实质,但是还有三个问题值得探讨

1. 假设事件队列本来为空,这时A, B两个事件相隔200ms发生,那么他们的回调例程是如何调度运行的呢?应该是这样:

  A事件的回调例程(简称A例程)先进入事件队列
  主线程发现队列来了一个例程实例,取出该实例
  主线程执行取出的例程实例
  主线程执行完毕,事件对例为空,主线程等待

  大约200ms 后
  B事件的回调例程(简称B例程)进入事件队列
  主线程发现队列来了一个例程实例,取出该实例
  主线程执行取出的例程实例
  主线程执行完毕,事件对例为空,主线程等待

OK, 没有意外的话,事件循环就这样一直的执行下去了,一切都很好。但是如果,A例程需要进行一些数据计算,执行时间大概需要500ms,那这时情况又是怎样的呢?应该是这样

A例程先进入事件队列
主线程发现队列来了一个例程实例,取出该实例
主线程执行取出的例程实例

主线程忙碌中

主线程忙碌中

大约200ms 后

B事件的回调例程(简称B例程)进入事件队列
(不管主线程忙碌与否,B例程都会进入事件队列)
主线程发现队列来了一个例程实例,取出该实例

主线程仍在执行A例程,忙碌中

大约500ms 后,主线程执行A例程完毕
主线程继续检查事件队列,发现里面有一个例程,于是取出B例程
主线程执行取出的B例程
主线程执行完毕,事件对例为空,主线程等待

从这里可以看出,如果A回调执行时间过长,会导致A,B回调的执行显得很连续,中间并不会间隔200ms左右

2. 所有的回调例程放入请求都会被事件队列接收吗?

答案是否,javascript引擎有点聪明,会保证事件队列中所有的例程实例都是不同的, 也就是说,在某些情况下,事件看起来好像会丢失!

3. javascript是单线程的吗?

不能简单的说是,且不说web workers的引入(当然是在browser端), 即使以我们前面的分析也可以看出,除了主线程之外,肯定还有另外的线程存在,比如将回调例程放入事件队列的那个线程。

当然,传统上,引擎暴露给我们的,我们能使用的就是主线程。

回到顶部