我在前一段时间偶然发现了node.js并且非常喜欢它。但很快我发现它缺乏执行CPU密集型任务的能力。所以,我开始使用谷歌搜索并得到这些答案来解决问题:纤维,Web工作者和线程(thread-a-gogo)。现在使用哪一个是一个混乱,其中一个肯定需要使用 - 毕竟有一个擅长IO的服务器的目的是什么?建议需要!
更新
我正在想办法离开;只是需要建议。现在,我想到的是:让我们有一些线程(使用thread_a_gogo或webworkers)。现在,当我们需要更多时,我们可以创造更多。但是在创建过程中会有一些限制。 (系统没有暗示,但可能是因为开销)。现在,当我们超出限制时,我们可以派生一个新节点,并开始在其上创建线程。这样,它可以持续到我们达到某个极限(毕竟,进程也有很大的开销)。达到此限制后,我们将开始排队任务。每当一个线程空闲时,它将被分配一个新任务。这样,它可以顺利进行。
所以,这就是我的想法。这个想法好吗?我对所有这些过程和线程都有点新意,所以没有任何专业知识。请分享您的意见。
感谢。 :)
答案 0 :(得分:326)
Node具有完全不同的范例,一旦正确捕获,就更容易看到这种解决问题的不同方式。您永远不需要Node应用程序中的多个线程(1),因为您有不同的方式来执行相同的操作。您创建了多个流程;但它与Apache Web Server的Prefork mpm的做法非常不同。
目前,让我们认为我们只有一个CPU核心,我们将开发一个应用程序(以节点方式)来完成一些工作。我们的工作是逐个字节地处理在其内容上运行的大文件。我们软件的最佳方法是从文件的开头开始工作,逐个字节地跟踪到最后。
- 嘿,哈桑,我想你要么是我祖父的新手还是非常老的学校!为什么不创建一些线程并使其更快?
- 哦,我们只有一个CPU核心。
- 那又怎样?创建一些线程man,让它更快!
- 它不是那样的。如果我创建线程,我将使它变慢。因为我将在系统中添加大量开销以在线程之间切换,试图给它们一定的时间,并在我的进程内尝试在这些线程之间进行通信。除了所有这些事实之外,我还必须考虑如何将一份工作分成多个可以并行完成的工作。
- 好的,我觉得你很穷。让我们使用我的电脑,它有32个核心!
- 哇,亲爱的朋友,你真棒,非常感谢你。我很感激!
然后我们回头工作。现在我们拥有32个cpu核心,感谢我们的朋友。我们必须遵守的规则刚刚改变了。现在我们想要利用我们获得的所有财富。
要使用多个核心,我们需要找到一种方法将我们的工作划分为可以并行处理的部分。如果它不是Node,我们会为此使用线程; 32个线程,每个cpu核心一个。但是,由于我们有Node,我们将创建32个Node进程。
线程可以是Node进程的一个很好的替代方案,甚至可能是更好的方法;但只有在已定义工作的特定工作中,我们才能完全控制如何处理工作。除此之外,对于工作来自外部的其他问题,我们无法控制,我们希望尽快回答,Node的方式无可争议地优越。
- 嘿,哈桑,你还在单线程吗?你有什么问题,伙计?我刚刚为你提供了你想要的东西。你没有任何借口了。创建线程,让它运行得更快。
- 我已将作品分成几部分,并且每个过程都会同时处理其中一个部分。
- 为什么不创建线程?
- 抱歉,我认为它不可用。你可以随身携带电脑吗?
- 不行,我很酷,我只是不明白为什么你不使用线程?
- 谢谢你的电脑。 :)我已经将工作分成几部分,我创建了并行处理这些部分的流程。所有CPU核心都将得到充分利用。我可以用线程而不是进程来做到这一点;但Node有这种方式,我的老板Parth Thakkar希望我使用Node。
- 好的,如果你需要另一台电脑,请告诉我。 :P
如果我创建了33个进程而不是32个进程,那么操作系统的调度程序将暂停一个线程,启动另一个进程,在一些循环后暂停它,再次启动另一个进程......这是不必要的开销。我不想要这个。实际上,在具有32个内核的系统上,我甚至不想创建正好32个进程,31个可以更好。因为不仅仅是我的应用程序可以在这个系统上工作。为其他东西留一点空间可能会很好,特别是如果我们有32个房间。
我相信我们现在在同一页面上关于充分利用 CPU密集型任务的处理器。
- 嗯,哈桑,我很抱歉嘲笑你一点。我相信我现在更了解你。但是我还需要解释一下:运行数百个线程的嗡嗡声是什么?我到处都读到线程比分叉进程更快创建和愚蠢?您分叉进程而不是线程,并且您认为它是使用Node获得的最高级别。然后Node不适合这种工作吗?
- 不用担心,我也很酷。每个人都说这些东西,所以我想我已经习惯了听到它们。
- 那么?节点对此不好?
- 即使线程也很好,节点对此非常有用。至于线程/进程创建开销;对于你重复很多的事情,每一毫秒都很重要。但是,我只创建了32个进程,这将花费很少的时间。它只会发生一次。它没有任何区别。
- 我什么时候想创建数千个线程呢?
- 您永远不想创建数千个线程。但是,在正在进行外部工作的系统上,例如处理HTTP请求的Web服务器;如果你为每个请求使用一个线程,你将创建很多线程,其中很多都是。
- 节点有所不同吗?正确?
- 是的,确切地说。这就是Node真正闪耀的地方。就像线程比进程轻得多,函数调用比线程轻得多。节点调用函数,而不是创建线程。在Web服务器的示例中,每个传入的请求都会导致函数调用。
- 嗯,有趣;但是如果不使用多个线程,则只能同时运行一个函数。当许多请求同时到达Web服务器时,它如何工作?
- 关于函数如何运行,一次一个,从不并行运行,你是完全正确的。我的意思是在一个进程中,一次只运行一个代码范围。操作系统调度程序不会暂停此功能并切换到另一个功能,除非它暂停进程以给其他进程留出时间,而不是我们进程中的另一个进程。 (2)
- 那么一个进程如何一次处理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的范例中,除非队列中的所有其他调用都设计为一个接一个地运行,否则永远不会有需要太长时间才能处理的函数。在计算成本高昂的任务的情况下,如果我们完整地看一下图片,我们就会发现这不是一个问题&#34;我们应该使用线程还是进程?&#34;但是问题是&#34;我们怎样才能将这些任务以均衡的方式划分为子任务,我们可以在系统中并行使用多个CPU内核来运行它们?&#34;我们假设我们将在具有8个内核的系统上处理400个视频文件。如果我们想一次处理一个文件,那么我们需要一个处理同一文件不同部分的系统,在这种情况下,多线程单进程系统可能更容易构建,甚至更高效。我们仍然可以通过运行多个进程并在需要进行状态共享/通信时在它们之间传递消息来使用Node。正如我之前所说,使用Node的多进程方法以及这种任务中的多线程方法;但不仅仅是那个。再次,正如我之前所说,Node发光的情况是当我们将这些任务作为来自多个源的系统输入时,因为与每个连接的线程或每个连接的进程相比,节点中的多个连接同时要轻得多系统
至于setTimeout(...,0)
来电;有时在耗时的任务中休息以允许队列中的呼叫可能需要它们的处理份额。以不同方式划分任务可以使您免于这些;但是,这不是真正的黑客攻击,它只是事件队列工作的方式。另外,使用process.nextTick
来实现这一目标要好得多,因为当你使用setTimeout
时,必须计算和检查所用的时间,而process.nextTick
就是我们真正想要的:&#34嘿任务,回到队列末尾,你已经用过了你的分享!&#34;
答案 1 :(得分:8)
我来自古老的思想流派,我们使用多线程来快速制作软件。在过去的3年里,我一直在使用Node.js和它的支持者。正如hasanyasin详细解释了节点如何工作以及异步功能的概念。但是,我在这里添加一些东西。
在过去,单核和更低的时钟速度,我们尝试了各种方法使软件快速并行地工作。在DOS天我们用一次运行一个程序。在Windows中我们开始一起运行多个应用程序(进程)。经过测试的先发制人和非先发制人(或合作社)等概念。我们现在知道抢占是单核计算机上更好的多处理任务的答案。随之而来的是流程/任务和上下文切换的概念。比线程的概念进一步减轻了进程上下文切换的负担。线程,作为产生新过程的轻量级替代品。
因此,无论是否信号线程或不是多核或单核,您的进程都将被操作系统抢占和时间切片。
Nodejs是一个单一进程并提供异步机制。在这里,当我们在事件循环中等待任务完成时,将工作分派到处于说谎的OS下以执行任务。一旦我们从OS获得绿色信号,我们就会执行我们需要做的事情。现在在某种程度上这是合作/非抢先的多任务,所以我们永远不应该阻止事件循环很长一段时间,否则我们会非常快地降低我们的应用程序。
因此,如果有任务本质上是阻塞的或非常耗时,我们将不得不将其分支到OS和线程的抢先世界。
在libuv documentation中有很好的例子。此外,如果您进一步阅读文档,则会发现FileI/O is handled in threads in node.js。
首先,它是我们软件设计的全部。其次,无论他们告诉你什么,上下文切换总是会发生。线程在那里并且仍然存在是有原因的,原因是它们在当时进程之间切换更快。
在node.js的引擎下,它的所有c ++和线程。节点提供c ++方式来扩展其功能,并通过使用线程来进一步加速,即阻塞任务,例如从源写入源读取,大数据分析等等。
我知道hasanyasin的答案是被接受的答案,但对我而言,无论你说什么或者你如何将它们隐藏在脚本后面,线程都会存在,其次没有人只是为了速度而将内容分解为线程,它主要用于阻塞任务。并且线程位于Node.js的后端,所以在完全打击多线程之前是正确的。线程也与进程不同,每个核心拥有节点进程的限制并不完全适用于线程数,线程就像进程的子任务。实际上线程赢了; t出现在你的windows任务管理器或linux top命令中。再一次,他们的重量再小于过程
答案 2 :(得分:4)
我不确定webworkers在这种情况下是否相关,它们是客户端技术(在浏览器中运行),而node.js在服务器上运行。据我所知,光纤也是阻塞的,即它们是自愿多任务处理,所以你可以使用它们,但是应该通过yield
自己管理上下文切换。线程可能实际上是你需要的,但我不知道它们在node.js中有多成熟。
答案 3 :(得分:3)
worker_threads
已实现,并在node@10.5.0
中的标志后面提供。它仍然是一个初始实现,需要做更多的工作才能使其在将来的版本中更有效。值得在最新的node
中尝试一下。
答案 4 :(得分:2)
在许多Node开发人员看来,Node的最佳部分之一实际上是它的单线程特性。线程为共享资源带来了一大堆困难,Node完全避免了除了非阻塞IO之外什么都不做。
这并不是说Node 限制到单个线程。只是获取线程并发的方法与您正在寻找的方法不同。处理线程的标准方法是使用Node本身标准的cluster模块。对于线程来说,这比在代码中手动处理它们更简单。
为了处理代码中的异步编程(如避免嵌套回调金字塔),Fibers库中的[Future]组件是一个不错的选择。我还建议您查看基于Fibers的Asyncblock。光纤很好,因为它们允许您通过复制堆栈然后在需要时在单线程上的堆栈之间跳转来隐藏回调。为您提供真正的线程麻烦,同时为您提供优势。缺点是使用光纤时堆栈跟踪可能会有点奇怪,但它们并不太糟糕。
如果你不需要担心异步的东西而且更多只是对没有阻塞的大量处理感兴趣,那么每隔一段时间就可以简单地调用process.nextTick(回调)。
答案 5 :(得分:1)
也许有关您正在执行的任务的更多信息会有所帮助。为什么你需要(如你在genericdave的回答中提到的那样)需要创建数千个?在Node中执行此类操作的常用方法是启动一个始终运行并可以使用消息进行通信的工作进程(使用fork或其他方法)。换句话说,每次你需要执行你正在做的任务时都不要启动一个新的工作人员,而只是向已经运行的工作人员发送一条消息,并在完成后得到响应。老实说,我不能看到启动数千的实际线程也非常有效,你仍然受到CPU的限制。
现在,在说完所有这些之后,我最近一直在用Hook.io做很多工作,这似乎对于将这种卸载任务转移到其他进程中非常有效,也许它可以完成你的任务需要。