Node具有完全不同的范例,一旦正确地捕获它,就更容易看到这种解决问题的不同方式。在Node应用程序(1)中,您永远不需要多个线程,因为您执行相同操作的方式不同。您创建多个流程;但这与Apache Web Server的Prefork mpm的做法非常不同。
现在,让我们认为我们只有一个CPU内核,我们将开发一个应用程序(以Node的方式)来完成一些工作。我们的工作是处理逐个字节运行其内容的大文件。对于我们的软件而言,最好的方法是从文件的开头开始工作,然后逐字节地跟踪文件的结尾。
-嘿,哈桑,我想你是我祖父时代的一所新手或老学校!!!为什么不创建一些线程并使它更快呢?
-哦,我们只有一个CPU核心。
- 所以呢?创建一些线程人,使其更快!
-它不是那样工作的。如果创建线程,我将使其变慢。因为我将在系统之间增加很多开销,以便在线程之间进行切换,因此尝试给它们以公平的时间,并在进程内部尝试在这些线程之间进行通信。除了所有这些事实之外,我还必须考虑如何将一个工作分解为多个可以并行完成的工作。
-好的,我知道你很穷。让我们使用我的计算机,它具有32核!
-哇,亲爱的朋友,你真棒,非常感谢。我很感激!
然后我们回去工作。现在我们有32个cpu内核,这要归功于我们丰富的朋友。我们必须遵守的规则刚刚改变。现在我们要利用我们得到的所有财富。
要使用多个内核,我们需要找到一种方法来将我们的工作分成可以并行处理的部分。如果不是Node,我们将为此使用线程。32个线程,每个cpu核心一个。但是,由于我们有Node,所以我们将创建32个Node进程。
线程可以很好地替代Node进程,甚至是更好的方法。但是仅在已经定义了工作并且可以完全控制其处理方式的特定工作中。除此之外,对于工作以外来的其他问题,我们无法控制,我们想尽快回答,Node的方法无疑是优越的。
-嘿,哈桑,你还在单线程工作吗?你怎么了,伙计?我刚刚为您提供了您想要的。你没有借口了。创建线程,使其运行更快。
-我将工作分为几部分,每个过程将并行处理其中的一部分。
-为什么不创建线程?
-对不起,我认为它不可用。如果需要,可以带计算机吗?
-不好,我很酷,我只是不明白为什么你不使用线程?
-谢谢您的电脑。:)我已经将工作分为几部分,并创建了并行处理这些部分的流程。所有CPU内核将得到充分利用。我可以用线程而不是进程来完成;但是Node有这种方式,我的老板Parth Thakkar希望我使用Node。
-好的,让我知道您是否需要另一台计算机。:p
如果我创建的不是33个进程,而是33个进程,则操作系统的调度程序将暂停一个线程,启动另一个线程,在某些周期后暂停它,然后再次启动另一个线程...这是不必要的开销。我不想要这个。实际上,在具有32个内核的系统上,我什至不想创建精确的32个进程,所以31个更好。因为不仅仅我的应用程序可以在该系统上运行。为其他事情留一点空间可能会很好,尤其是如果我们有32个房间。
我相信我们现在在同一页上,关于充分利用处理器来执行CPU密集型任务。
-嗯,哈桑(Hasan),很抱歉对你有些嘲笑。我相信我现在对您的了解更好。但是我仍然需要解释一下:关于运行数百个线程的所有嗡嗡声是什么?我到处都读到,创建线程和创建线程要比派生进程快得多?您派生了进程而不是线程,并且您认为它是使用Node获得的最高性能。那么,Node不适合这种工作吗?
-不用担心,我也很酷。每个人都说这些话,所以我想我已经习惯了。
-那么?节点对此不好吗?
-即使线程也可以很好,Node对此非常有用。至于线程/进程创建的开销;在重复很多的事情上,每一毫秒都很重要。但是,我仅创建32个进程,这将花费很少的时间。它只会发生一次。不会有任何区别。
-那么我什么时候要创建数千个线程?
-您永远不想创建数千个线程。但是,在执行来自外部的工作的系统上,例如处理HTTP请求的Web服务器。如果您为每个请求使用一个线程,则将创建很多线程,其中很多。
-节点不一样吗?对?
- 对,就是这样。这是Node真正发挥作用的地方。就像线程比进程轻得多,函数调用比线程轻得多。节点调用函数,而不是创建线程。在Web服务器的示例中,每个传入请求都导致一个函数调用。
-嗯,很有趣;但是,如果不使用多个线程,则只能同时运行一个函数。当许多请求同时到达Web服务器时,该如何工作?
-您对函数的运行方式完全正确,一次只运行一次,永远不会并行运行两个。我的意思是在一个过程中,一次只运行一个范围的代码。OS Scheduler不会出现并暂停此功能并切换到另一个功能,除非它暂停该进程以给另一个进程(而不是我们进程中的另一个线程)留出时间。(2)
-那么一个进程如何一次处理两个请求?
-只要我们的系统具有足够的资源(RAM,网络等),一个进程就可以一次处理数万个请求。这些功能的运行方式是关键区别。
-嗯,我现在应该兴奋吗?
-也许:)节点在队列上运行循环。我们的工作在这个队列中,即我们开始处理传入请求的呼叫。这里最重要的一点是我们设计函数运行的方式。在开始可接受的工作量之后,我们快速结束函数,而不是开始处理请求并使调用者等待完成任务。当我们需要等待另一个组件完成一些工作并返回一个值,而不是等待该值时,我们只需完成函数即可将其余工作添加到队列中。
-听起来太复杂了吗?
-不,我听起来可能很复杂;但是系统本身非常简单,非常合理。
现在,我要停止引用这两个开发人员之间的对话,并在最后一个有关这些功能如何工作的快速示例之后结束我的回答。
这样,我们正在执行OS Scheduler通常会执行的操作。我们暂停工作,并让其他函数调用(如多线程环境中的其他线程)运行,直到再次轮到我们为止。这比将工作留给OS Scheduler更好,后者试图给系统上的每个线程仅时间。我们知道我们的工作要比OS Scheduler好得多,并且应该在应该停止时停止。
下面是一个简单的示例,其中我们打开一个文件并读取它以对数据进行一些处理。
同步方式:
Open File
Repeat This:
Read Some
Do the work
异步方式:
Open File and Do this when it is ready: // Our function returns
Repeat this:
Read Some and when it is ready: // Returns again
Do some work
如您所见,我们的功能要求系统打开文件,而不等待文件被打开。文件准备好后,通过提供后续步骤来完成自身。当我们返回时,Node在队列上运行其他函数调用。运行完所有功能后,事件循环移至下一轮。
总之,Node具有与多线程开发完全不同的范例。但这并不意味着它缺少东西。对于同步作业(我们可以确定处理的顺序和方式),它与多线程并行性一样有效。对于诸如请求服务器之类的来自外部的作业,它简直是上乘。
(1)除非使用其他语言(例如C / C ++)构建库,否则在这种情况下,您仍然不会创建用于划分作业的线程。对于这种工作,您有两个线程,一个线程将继续与Node通讯,而另一个线程则进行实际工作。
(2)实际上,由于我在第一个脚注中提到的相同原因,每个Node进程都有多个线程。但是,这绝不是1000个线程做类似的工作。这些额外的线程用于接受IO事件并处理进程间消息传递。
更新(作为对评论中一个好的问题的答复)
@Mark,感谢您的建设性批评。在Node的范例中,除非将队列中的所有其他调用都设计为一个接一个地运行,否则永远不要有需要花费太长时间处理的函数。如果计算任务比较繁琐,那么如果我们完整地看一下图片,就会发现这不是“我们应该使用线程还是进程?”的问题。但是有一个问题:“如何将这些任务均衡地划分为子任务,以便可以使用系统上的多个CPU内核并行运行它们?” 假设我们将在具有8个核心的系统上处理400个视频文件。如果我们想一次处理一个文件,那么我们需要一个能够处理同一文件的不同部分的系统,在这种情况下,也许多线程单进程系统将更易于构建并且效率更高。我们仍然可以通过运行多个进程并在需要状态共享/通信时在它们之间传递消息来使用Node。如前所述,使用Node的多进程方法是以及此类任务中的多线程方法;但不止于此。再次,正如我之前所讲的,Node的亮点是当这些任务作为来自多个源的输入作为系统输入时,因为与每个连接线程或每个连接进程相比,在Node中同时保持多个连接要轻得多系统。
至于setTimeout(...,0)
通话;有时,在耗时的任务期间要休息一下,以允许队列中的呼叫可以共享其处理量。以不同方式划分任务可以使您免于这些任务;但这并不是真正的hack,它只是事件队列的工作方式。同样,process.nextTick
为此目的使用更好,因为当您使用时setTimeout
,有必要计算和检查经过的时间,而process.nextTick
这正是我们真正想要的:“嘿,任务,回到队列的末端,您已经使用了您的份额! ”