Node JS单线程与多线程(CPU利用率:有区别吗?)

10
我最近开始阅读很多与Node JS相关的内容,有一件事情从分化的角度来看我还没有能够清楚地理解,那就是异步调用和同步调用在I/O处理方面的真正区别。
据我所知,在一个多线程的同步环境中,如果启动了I/O操作,运行的线程将被抢占,并回到等待状态。因此,这与NodeJS异步I/O调用所发生的情况实质上是相同的。在NodeJS中,当调用I/O时,I/O操作会移出当前正在运行的线程并发送到事件去重复器进行完成和通知。一旦I/O完成,回调方法将被推送到事件队列进行进一步处理。
因此,我唯一看到的区别就是在NodeJS中我们正在节省内存(由于每个线程拥有多个调用堆栈),并且CPU也在节省(因为没有上下文切换)。如果我只考虑我有足够的内存购买,那么仅仅因为上下文切换而节省CPU的性能差异是否如此之大呢?
如果我的理解不正确,那么在处理I/O方面,Java线程与NodeJS在保持CPU繁忙和不浪费CPU周期方面有何不同?我们只是通过NodeJS节省了上下文切换的CPU周期,还是还有更多?
基于回应,我想添加另一种情况:
请求A和请求B同时到达J2ee服务器。在这个多线程环境中,每个请求需要10毫秒才能完成。在10毫秒内,5毫秒用于执行代码逻辑以计算一些逻辑,5毫秒用于I/O,从DBMS中拉取大型数据集。DBMS的调用是代码的最后一行,在此之后,响应应该发送到客户端。
如果将同一应用程序转换为Node JS应用程序,则可能会发生以下情况:
1.请求A到来,使用5 ms处理请求。 2.DBMS调用被击中,但它是非阻塞的。因此,回调方法被推送到事件队列。 3.经过5毫秒,服务请求B并再次将请求B推送到事件队列以完成I/O请求。
  • 处理需要花费5毫秒时间。
  • 事件循环运行,接收请求A的回调函数处理程序,然后将响应发送给客户端。因此,在同步代码块处理中,请求A和请求B都需要花费5毫秒,所以响应在10毫秒后发送。
  • 那么在这种情况下节省的时间在哪里?除了上下文切换和创建2个线程外,使用Node JS时Req A和Req B都需要花费10毫秒的时间。?


    1
    你的例子是一个单一事件序列,每个事件都依赖上一个事件完成后才能进行下一个。Node并不能加速这个过程,事实上从理论上讲这是不可能的,因为你需要上一个任务的值来执行下一个任务。Node擅长的是处理多个这样的事件,可以并发地处理它们,而不仅仅是一次只能处理一个。就并发性或并行性而言,你的例子并没有太多意义。 - tsturzl
    从未创建任何线程,也没有切换上下文。我认为这个答案应该被关闭,因为我认为它无法回答。 - tsturzl
    时间被节省了,因为它不会阻塞执行,而是可以继续处理其他事情,但在同一个串行任务中,你不能没有前一个任务的值就继续进行。这是一种更好的线程处理方法,就如我在下面的回答中所解释的。上下文切换需要内存和CPU开销。它很重,不适合并发IO。像Node.js提供的事件驱动或流式并发方式轻量级且非常适合IO,类似的还有协程。这些方法需要较少的分配、上下文切换和内存消耗。 - tsturzl
    基本上是为了性能和减少开销而牺牲低级别控制。上下文切换很重,分配内存也很重(通常是线程池的原因),对于IO来说,这些低级别控制是不需要的。线程本质上类似于进程进行调度,它们切换上下文需要很多CPU周期。即使在线程池中,线程也很昂贵。你节省的不仅仅是内存,还有很多无用的上下文切换工作,这不是一项轻松的任务。 - tsturzl
    http://learn-gevent-socketio.readthedocs.org/en/latest/general_concepts.html - tsturzl
    1个回答

    5
    我理解,在多线程同步环境下,如果启动 I/O 操作,则运行的线程会被抢占并回到等待状态。因此,这与 NodeJS 异步 I/O 调用发生的情况基本相同。 不,NodeJS 中的异步 I/O 调用是非阻塞 I/O。这意味着一旦线程发出 I/O 调用,它就不会等待 I/O 完成并继续执行下一个语句/任务。
    一旦 I/O 完成,它会从 事件循环队列 中获取下一个任务,并最终执行在进行 I/O 调用时给定的回调处理程序。
    如果我只考虑有足够的内存可以购买,那么仅通过上下文切换节省 CPU 的性能差异是否很大?除此之外,还有两个方面的节省。
    • 不必等待I/O完成
    • 不必创建线程,因为线程是有限的,所以系统的容量不受它可以创建多少个线程的限制。

    除了上下文切换和创建2个线程。使用Node JS,请求A和请求B都需要10毫秒。?

    你在这里忽略了一件事-线程在特定间隔后接连获得两个请求。因此,如果一个线程需要10秒钟,那么将需要一个新线程来执行第二个请求。将此推广到成千上万的请求和您的操作系统需要处理如此多的并发用户的情况。请参考此类比


    @learnerFromHell,差异在于-1)上下文切换较少2)线程较少3)等待CPU周期的时间较短,因为磁盘操作的负担(虽然不太需要CPU,但可能具有高时间成本)不由nodejs应用程序承担。 - gurvinder372
    如果在多线程环境中,请求A的处理需要10毫秒,并且等待5毫秒才能完成IO操作(线程被卡住,响应未发送),那么在Node JS中,这5毫秒会被保存吗?无论如何,直到5毫秒的IO操作完成后,请求的响应才会被发送回来,因为只有当IO的回调完成时,响应才会被发送。我错在哪里了? - learnerFromHell
    它们是完全不同的范式。 - tsturzl
    2
    回调函数并不是唯一的逻辑。回调函数只有在完成后才会被调用,但是假设你有另一个请求进来,你可以在同一个线程上处理它,因为你的线程没有被阻塞。 - tsturzl
    1
    如果您的应用程序在调用I/O之前要花费5毫秒来处理每个请求,那么您可以使用单个线程获得200个并发请求的能力(否则需要使用200个不同的线程才能实现)。但是,如果I/O很耗时,则最终吞吐量显然会降低。您显然需要权衡这种方法的利弊,并相应地进行设计。Nodejs并不保证在每种情况下都具有更好的性能,它的设计目的是增加容量,而不一定是性能。 - gurvinder372
    显示剩余10条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接