美文网首页我爱编程
[译]理解node.js事件轮询

[译]理解node.js事件轮询

作者: 西麦smile | 来源:发表于2018-04-23 13:44 被阅读101次

    在ManUel Kiessling的《The Node Beginner Book》中提到了Node.js的事件轮询。

    其中提到Mixu的博文:《Understanding the node.js event loop》

    原文出处:http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/

    以下是我的译文:

    关于Node.js的第一个基本概念是I/O操作的开销是巨大的:

    The-cost-of-IO.jpg

    因此在现代编程技术中,等待I/O操作完成是最浪费时间的。这里列举了几种方式来解决这种性能影响,(来自 Sam Rushing):

    • 同步处理:你一次处理一个请求,每个请求依次处理。优点:简单;缺点:任何一个请求都会阻塞其他请求。
    • 开启一个新进程:你为每个请求开启一个新的进程。优点:简单;缺点:不利于扩展,大量的链接意味着大量的进程。fork()是Unix编程的一个锤子,因为它非常有用,因此常被过度使用来解决任何看起来像一个钉子的问题。
    • 线程:为每个请求开启一个新的线程。优点:简单,而且比开启新进程对内核更友好,因为线程的开销通常会更小一些;缺点:不是所有的机器都支持线程,而且对于要处理共享资源的情况,多线程会很快变得过于复杂。

    第二个基本概念是,如果要为每个线程都开启一个新的链接,这样的内存开销是巨大的(比如:和Nginx相比之下,Apache内存耗尽的情况)。

    Apache是多线程的:它为每个请求开启一个新的线程(或进程,这取决于实际配置)。随着并发连接数量的增加,以及需要更多线程同时为多个的客户端服务时,你可以看到它是如何消耗内存的。Nginx和Node.js不是多线程的,因为线程和进程的内存开销太大了。它们是单线程、基于事件的。它解决了处理众多连接所产生的线程/进程的消耗的问题。

    Node.js让你的代码保持单线程...

    它是真的只有一个线程在运行:你不能并行地执行程序;例如模拟一个"sleep"会阻塞服务器一秒钟:

    while(new Date().getTime() < now + 1000) {  
       // do nothing  
    }
    

    因此当上面这段代码在执行的时候,node.js将不会响应任何其它来自客户端的请求,因为它只能有一个线程来执行你的代码。此外,如果你执行cpu密集任务,比如重设图像的大小,它也会阻塞所有请求。

    ...然而,除了你的代码,其它的一切都是并行执行的

    单线程没办法让代码并行执行。然而,所有的 I/O 都是事件驱动、异步的。所以下面的代码不会阻塞server:

     c.query(
       'SELECT SLEEP(20);',
       function (err, results, fields) {
         if (err) {
           throw err;
         }
         res.writeHead(200, {'Content-Type': 'text/html'});
         res.end('&lt;html&gt;&lt;head&gt;&lt;title&gt;Hello&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;h1&gt;Return from async DB query&lt;/h1&gt;&lt;/body&gt;&lt;/html&gt;');
         c.end();
        }
    );
    

    如果你在一次请求中执行上面的代码,当数据库sleep时,其它请求也会被立即处理。

    为什么异步更好?我们应该什么时候从同步转移到异步/并行执行呢?

    使用同步执行也是不错的,因为它简化了代码的编写(相比于多线程,并发性问题有导致WTFs的趋势)(译者注:WTF === What The Fuck)。

    在Node.js中,你不必担心后台是怎么处理的:当你在做I/O操作时只需要使用回调就可以了;而且它保证了你的代码永远不会中断,I/O操作不会阻塞其它请求,同时也无需承担每个请求所产生的线程/进程的开销成本(例如Apache中的内存开销)。

    在I/O操作中使用异步是很好的选择,因为I/O操作的开销比单纯地执行代码要高得多,我们不应该单纯地等待I/O操作,而是应该在这时做一些事情。

    bucket_3.jpg

    事件轮询是“一个掌握和处理外部事件并且把他们转成回调调用的实体”。因此I/O调用的同时,server就可以去处理另一个请求。在一次I/O调用中,你的代码会保存回调函数并把控制权返回到node.js运行时。当数据可访问时,就可以执行这个回调了。

    当然,在后端,还是有数据库访问和流程执行的线程和进程。但是,这些都不需要你的代码直接实现,因此除了了解I/O交互之外,你不了解它们。比如,从每个请求的角度来看,数据库或其它流程需要异步,因为这些线程的结果会通过事件轮询返回给你代码。和Apache模块相比,它省去了许多内存的消耗,因为不是每个链接都需要更新线程;只有当你真正确定某些进程是并行运行时才会更新线程,即使这样的操作也是通过Node.js来处理的。

    除了I/O调用以外,Node.js所期望的所有请求都能被快速返回;比如CPU密集型任务分解到另一个可与事件交互的进程,或使用WebWorkers等抽象交互时。这(显然)意味你不能并行执行你的代码,除非后台有个另一个线程,你可以通过事件与之交互。基本上,所有发出事件的对象(例如 EventEmitter实例)都支持异步均衡交互。并且可以用这种方式组织代码交互,比如在Node.js中使用文件、套接字或子进程等EventEmitters。多核可以使用这种方法;另见:node-http-proxy。

    内部实现

    内部,node.js依靠libev来提供事件轮询,这是libeio的补充,它使用线程池来提供异步I/O操作。要了解更多信息,请参阅libev文档

    因此我们应该如何在Node.js中实现异步?

    Tim Caswell在他的演讲中描述了这些模式:

    • First-class Function(译者注:该类型的值可以作为函数的参数和返回值,也可赋给变量)。比如,我们将函数作为数据传递,并在需要时执行它们。
    • Function composition。又称为匿名函数或闭包,在基于时间的I/O中发生某种事件时执行。

    相关文章

      网友评论

        本文标题:[译]理解node.js事件轮询

        本文链接:https://www.haomeiwen.com/subject/uxtllftx.html