关于事件轮询(event loop)的问题

12-03-12 lovejdon
         

最近看了看Node.js的东西,在单线程下执行非阻塞i/o,依据事件轮询来达到提高并发的效率。这一点可以理解。但是我深入想象内部实现,应该也是开了后台线程,对于每一个I/O操作定义的回调进行注册,然后依次执行回调方法。不知道是否是这样的,如果是这样的,那么所谓轮询,也就是后台线程做个无线循环去队列获取注册的回调事件,但是由于回调方法中可能涉及到数据库或者I/O操作,那又是耗时的地方,可以肯定的是,必然会有一个地方去执行耗时的操作,应该就是在后台线程中,那个人认为后台线程应该是不止一个来做,而是多个线程去处理。不知道这里理解的是否正确,确实有点迷惑了。mixu的事件轮询博文我看了,实际的实现方式是利用Libev这个东西,但是是基于C的,所以也没去看,不知道具体事件轮询的大概原理是否理解的正确,请banq指点下,多谢!

         

banq
2012-03-12 13:41

2012年03月12日 08:28 "@lovejdon"的内容
事件轮询 ...

事件轮询主要是针对事件队列进行轮询,事件生产者将事件排队放入队列中,队列另外一端有一个线程称为事件消费者会不断查询队列中是否有事件,如果有事件,就立即会执行,为了防止执行过程中有堵塞操作影响当前线程读取队列,事件消费者线程会委托一个线程池专门执行这些堵塞操作。

Javascript前端和Node.js的机制类似这个事件轮询模型,有的人认为Node.js是单线程,也就是事件消费者是单线程不断轮询,如果有堵塞操作怎么办,不是堵塞了当前单线程的执行吗?

其实Node.js也有一个线程池,线程池专门用来执行各种堵塞操作,这样不会影响单线程这个主线程进行队列中事件轮询和一些任务执行,线程池操作完以后,又会作为事件生产者将操作结果放入同一个队列中。

总之,一个事件轮询Event Loop需要三个组件:事件队列,队列的读取轮询线程以及另外一个线程池。

在Node.js中,因为只有一个单线程不断地轮回查询队列中是否事件,对于数据库 文件系统等I/O操作,包括HTTP请求等等这些容易堵塞等待的操作,如果也是在这个单线程中实现,肯定会堵塞影响其他工作任务的执行,Javascript/Node.js会委托给底层的线程池执行,并会告诉线程池一个回调函数,这样单线程继续执行其他事情,当这些堵塞操作完成后,其结果与提供的回调函数一起再放入队列中,当单线程从队列中不断读取事件,读取到这些堵塞的操作结果后,会将这些操作结果作为回调函数的输入参数,然后激活运行回调函数。

请注意,Node.js的这个单线程不只是负责读取队列事件,还会执行运行回调函数,这是它区别于多线程模式的一个主要特点,多线程模式下,单线程只负责读取队列事件,不再做其他事情,会委托其他线程做其他事情,特别是多核的情况下,一个CPU核负责读取队列事件,一个CPU核负责执行激活的任务,这种方式最适合很耗费CPU计算的任务。反过来,Node..js的执行激活任务也就是回调函数中的任务还是在负责轮询的单线程中执行,这就注定了它不能执行CPU繁重的任务,比如JSON转换为其他数据格式等等,这些任务会影响事件轮询的效率。

Node.js的事件轮询Event Loop原理解释

[该贴被banq于2015-09-30 09:10修改过]

luda
2015-09-29 20:18

nodejs应该会有个管理和排队所有事件的队列,它每次执行了一个过程得到返回后都去队事件队列里去出队一个事件消息然后传入对应的回调函数中去,这里就像是发布订阅模式,而这个回调函数则是那个消息的唯一订阅者。它这样每次得到一个过程的返回后就到事件队列那里走一趟,每次都会经过同一个位置绕一圈然后进入一个新的圈,看起来像周期、像轮训。

注意:虽然回调函数是唯一订阅者,但是这个回调方法里面可以有很多行过程调用,比如有

alert(result.Id);

showResult(result);

这样两行代码,这两行代码又开启了两个新的过程,这两个新过程都能访问(消费)回调方法传入的result。

nodejs的那种单线程跟操作系统的多线程两种模式估计是等效的。操作系统齐头并进推进所有的线程往前走的时候也是得每完成一个“过程”(这里的过程加了引号,这里的过程不是编程语言层次的function定义的过程,而是若干个时间片)后都得跑到线程列表中去看一下从中挑个优先级高的进去。如果cpu只有一个的话就是跟nodejs的单线程是一样的运行时景象了,计算机走过的必定是一条连续的线。这条线反复经过那个集中管理线程的那个地点,这样看的话也是周期、也是轮训。两者都存在一个被反复经过的地点。只是每次经过那个地点的时候是在不同的时刻经过的,单核cpu走过的路径一定是一条连续的线,cpu会反复经过同一个地点,这会使人想到周期、循环、波、震荡……

被单核cpu驱动的那个唯一的线程就是个人,是人在资源空间中行走。而这个唯一的人又可以分神,但它的本神只有一个。单核cpu绘制出的多线程中的每一个线程是cpu的一个分神。我也不理解分神这个词,我是感觉这个词可以望文生义所以用这个词的。那个人能行走是因为有来自计算机外部的能量驱动着的它。

操作系统里的那一套东西好像也在到处分神(分形),nodejs算是操作系统那套东西的一个分神吧。反正世界竟是分形的竟是到处都一样的。

我大都是臆测的,臆测的原则只有一个:一个人不能在同一个时刻出现在不同的空间。

不管量子力学怎么说我都永不放弃这个原则。因为我发现只有基于它才能让我解释世界,没了它我啥也解释不了。

luda
2015-09-29 20:31

2015-09-29 20:18 "@luda"的内容
一个人不能在同一个时刻出现在不同的空间。 ...

计算机中可能蕴含着所有已知的物理定律。

luda
2015-09-30 12:09

2015-09-29 20:18 "@luda"的内容
nodejs应该会有个管理和排队所有事件的队列,它每次执行了一个过程得到返回后都去队事件队列里去出队一个事件消息然后传入对应的回调函数中去,这里就像是发布订阅模式,而这个回调函数则是那个消息的唯一订阅者。它这样每次得到一个过程的返回后就到事 ...

这一段修正为更精确的表述:

nodejs应该会有个管理和排队所有事件的队列,它每次执行了一个过程得到返回后,这个返回如果不是void而且有人声称将要回调它(消费它)的话就把这个返回值连同回调方法的地址一起入队到队列尾(这是一种组合了这个返回值和对应的回调方法的结构体,这个结构体可以被称作事件),然后去事件队列头去出队一个事件消息然后传入对应的回调函数中去,这里就像是发布订阅模式,而这个回调函数则是那个消息的唯一订阅者。它这样每次得到一个过程的返回后就到事件队列那里走一趟,每次都会经过同一个位置绕一圈然后进入一个新的圈,看起来像周期、像轮训。

注意:虽然回调函数是唯一订阅者,但是这个回调方法里面可以有很多行过程调用,比如有

alert(result.Id);

showResult(result);

这样两行代码,这两行代码又开启了两个新的过程,这两个新过程都能访问(消费)回调方法传入的result。



红色线是计算机的cpu在资源空间中走过的路径。像什么?孢子?植物?动物?大分子?原子……

那个cpu就是我们,是我们在这个世界中行走。我突然发现我们走的居然一直都是这么自然。

每一个椭圆是一个子过程,有可能这跟星空和原子的那种椭圆运动是一回事,在我们看起来是个椭圆,其实有可能是个栈的出入队。

想象力比知识更重要,身在庐山中的程序员不利于发挥对庐山的想象力。只有庐山外的人在未进入庐山之前才会有更多的想象力。

我们在计算机这个领域肯定是会比其它领域的人知识更多,但我们对这个领域的想象力可能会不如旁领域的人多。但我们对旁领域的想象力有可能会多于他们。

如果身在庐山中还能保持对庐山的想象力,或者能够把对庐山之外的事物的想象力基于某种对应的分形能力对应到庐山内的话。那么这样的人是圣人。

想象力不是随机的瞎想,而是走的连续的线。以永远不相信“一个人可以在同一时刻出现在不同的地点”为基础展开想象力。得能使用连续解释随机,以此为出发点进行想象,否则就是瞎想。永远不要相信同一个人可以在同一时刻出现在不同的地方。量子力学并没有说过一个人可以在同一时刻出现在不同的地点。

2Go 1 2 下一页