哪个更好的node.js并发任务? 纤维? networking工作人员? 或线程?
我前段时间偶然发现了node.js,喜欢它。 但很快我发现它缺乏执行CPU密集型任务的能力。 所以,我开始使用Google,并得到这些答案来解决这个问题:纤维,networking工作者和线程(thread-a-gogo)。 现在哪一个使用是一个混乱,其中一个肯定需要使用 – 毕竟有一个服务器,只是在IO好,没有别的目的是什么? 需要build议!
更新:
我正在想办法, 只是需要build议。 现在,我想到的是:让我们有一些线程(使用thread_a_gogo或者webworkers)。 现在,当我们需要更多的时候,我们可以创造更多。 但是创作过程会有一些限制。 (不是由系统暗示,但可能是因为开销)。 现在,当我们超出限制时,我们可以分叉一个新的节点,并开始创build线程。 这样,它可以继续下去,直到我们达到一定的限度(毕竟,进程也有一个很大的开销)。 当达到这个限制时,我们开始排队任务。 每当一个线程变为空闲时,它将被分配一个新的任务。 这样,它可以顺利进行。
所以,那是我想到的。 这个想法好吗? 我对这个过程和线程有点新鲜,所以没有任何专业知识。 请分享你的意见。
谢谢。 🙂
节点有一个完全不同的范例,一旦被正确捕获,更容易看到这种解决问题的不同方式。 你从来不需要Node应用程序中的多个线程(1),因为你有不同的方式来做同样的事情。 您创build多个进程; 但是它与Apache Web Server的Prefork MPM是如何不同的。
现在,让我们认为我们只有一个CPU核心,我们将开发一个应用程序(以Node的方式)来做一些工作。 我们的工作是处理一个大文件逐字节地运行其内容。 我们的软件最好的方法是从文件的开头开始工作,逐字节地结束。
– 嘿,哈桑,我想你是我爷爷时代的新手或者老派! 你为什么不创build一些线程,并使其更快?
– 哦,我们只有一个CPU核心。
– 所以呢? 创build一些线程的人,使其更快!
– 这不行。 如果我创build线程,我会让它变慢。 因为我将会在系统之间join大量的开销,以便在线程之间进行切换,试图给他们一些时间,并且在我的进程内部尝试在这些线程之间进行通信。 除了所有这些事实之外,我还必须考虑如何将一份工作分成多个可以并行完成的工作。
好的好吧,我看你很穷 让我们用我的电脑,它有32个核心!
哇,亲爱的朋友你真棒,非常感谢。 我很感激!
然后我们回去工作。 现在我们有32个cpu内核,这要感谢我们的朋友。 我们必须遵守的规则刚刚改变。 现在我们要利用所有这些财富。
要使用多个核心,我们需要find一种方法将我们的工作分成几块,并行处理。 如果不是Node,我们会使用线程来实现这个function。 32个线程,每个CPU核心一个。 但是,由于我们有节点,我们将创build32个节点进程。
线程可以是Node进程的一个很好的select,甚至可能是一个更好的方法; 但是只有在已经确定了工作的特定types的工作中,我们才能完全控制如何处理工作。 除此之外,对于其他任何一种工作来自外部的问题,我们都无法控制,我们希望尽快回答,Node的方式是无可争议的优越。
– 嘿,哈桑,你还在单线程吗? 你怎么了,男人? 我刚刚提供了你想要的东西。 你已经没有任何借口了。 创build线程,使其运行速度更快。
– 我把这些作品分成了几部分,每一个过程都可以并行处理其中的一个部分。
– 为什么你不创build线程?
对不起,我不认为这是可用的。 你可以把你的电脑,如果你想要的?
– 没关系,我很酷,我只是不明白你为什么不使用线程?
– 谢谢你的电脑。 :)我已经把工作分成了几部分,并且我创build了并行处理这些部分的stream程。 所有的CPU核心将被充分利用。 我可以用线程来代替进程, 但是Node有这个方法,我的老板Parth Thakkar要我使用Node。
– 好的,让我知道你是否需要另一台电脑。 :p
如果我创build了33个进程,而不是32个,操作系统的调度程序将暂停一个线程,启动另一个线程,在一些循环后暂停,再次启动另一个线程…这是不必要的开销。 我不想要这个。 事实上,在32核的系统上,我甚至不想创build32个进程,31个可以更好 。 因为这不只是我的应用程序,将在这个系统上工作。 留下一个小房间可以很好,特别是如果我们有32个房间。
我相信我们现在正在为处理器密集型任务充分利用处理器。
– 呃,哈桑,我很抱歉嘲笑你一点。 我相信我现在更了解你了。 但是我仍然需要一个解释:关于运行数百个线程的嗡嗡声是什么? 我在任何地方都能看到线程比分支进程创build和运行要快得多。 你分叉进程,而不是线程,你认为这是最高的,你会得到与节点。 那么Node是不是适合这种工作呢?
– 不用担心,我也很酷。 大家都说这些东西,所以我觉得我习惯了听。
– 那么? 节点对此不好?
– 即使线程也可以很好,Node对于这个也是非常好的。 至于线程/进程创build的开销; 在你重复的事情上,毫秒数。 但是,我只创build了32个进程,只需要很less的时间。 它只会发生一次。 这不会有什么区别。
– 我什么时候想创build数千个线程呢?
– 你永远不想创build数千个线程。 但是,在外部工作的系统上,像处理HTTP请求的Web服务器, 如果你为每个请求使用一个线程,你将会创build大量的线程,其中很多。
– 节点是不同的,但? 对?
– 对,就是这样。 这是节点真正发光的地方。 就像线程比进程轻得多,函数调用比线程轻得多。 节点调用函数,而不是创build线程。 在Web服务器的例子中,每个传入的请求都会导致函数调用。
– 嗯,有意思 但如果不使用多个线程,则只能同时运行一个函数。 当大量请求同时到达Web服务器时,这怎么能工作?
– 关于function如何运行是完全正确的,一次一个,两个并行。 我的意思是在一个进程中,一次只能运行一个代码范围。 操作系统调度程序不会来暂停这个function,并切换到另一个,除非它暂停进程给时间给另一个进程,而不是在我们的进程中的另一个线程。 (2)
– 那么一个进程如何一次处理2个请求呢?
– 只要我们的系统有足够的资源(RAM,networking等),一个进程就可以同时处理数以万计的请求。 这些function如何运行是关键的差异。
– 嗯,我现在应该兴奋吗?
– 也许:)节点在队列上运行一个循环。 在这个队列中是我们的工作,即我们开始处理传入请求的调用。 这里最重要的一点是我们devise我们的function运行的方式。 我们没有开始处理请求,而是让主叫方等待,直到我们完成工作,我们在做了可以接受的工作之后迅速结束了我们的function。 当我们需要等待另一个组件做一些工作并返回给我们一个值时,我们只需完成将剩余工作join到队列中的function即可。
– 这听起来太复杂了?
– 不,不,我可能听起来很复杂。 但系统本身非常简单,而且非常有意义。
现在,我想停止引用这两个开发人员之间的对话,并在这些function如何工作的最后一个快速示例之后完成我的答案。
这样,我们正在做什么操作系统调度程序通常会做。 我们暂停工作,让其他函数调用(像multithreading环境中的其他线程)运行,直到我们再次轮到我们。 这比将工作交给OS Scheduler要好得多,它试图给系统上的每个线程一点时间。 我们知道我们所做的比OS Scheduler要好得多,当我们停下来的时候我们会停下来。
下面是一个简单的例子,我们打开一个文件并阅读它来完成一些数据的工作。
同步方式:
Open File Repeat This: Read Some Do the work
asynchronous方式:
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
正如你看到的,我们的function要求系统打开一个文件,不要等待打开。 通过在文件准备好后提供下一个步骤完成本身。 当我们返回时,Node在队列上运行其他函数调用。 运行完所有的函数后,事件循环移动到下一个回合…
总之,Node具有与multithreading开发完全不同的范例; 但这并不意味着它缺less的东西。 对于一个同步作业(我们可以决定处理的顺序和方式),它和multithreading并行一样。 对于来自外部的工作,如对服务器的请求,它就是优越的。
(1)除非你正在用C / C ++等其他语言来构build库,在这种情况下,你仍然不会创build用于分割作业的线程。 对于这种工作,你有两个线程,其中一个将继续与Node进行通信,而另一个则是真正的工作。
(2)实际上,每个Node进程都有多个线程,这与我在第一个脚注中提到的相同的原因是一样的。 然而,这不是像1000个线程做类似的工作。 这些额外的线程是为了接受IO事件和处理进程间消息。
更新(作为在评论中回答一个好问题)
@马克,谢谢你的build设性的批评。 在Node的范例中,除非队列中的所有其他调用被devise为一个接一个地运行,否则不应该有需要太长时间处理的函数。 如果计算成本高昂,我们看完整的图片后,发现这不是“我们应该使用线程还是进程?”的问题。 而是一个“我们如何能够平衡地将这些任务分成子任务,我们可以在系统上并行运行多个CPU核心”? 假设我们将在具有8个内核的系统上处理400个video文件。 如果我们想一次处理一个文件,那么我们需要一个系统来处理同一文件的不同部分,在这种情况下,multithreading单进程系统可能更容易构build,甚至更高效。 当状态共享/通信是必要的时,我们仍然可以通过运行多个进程并在它们之间传递消息来使用Node。 正如我之前所说的,在这种任务中,使用Node的多进程方法和multithreading方法是一样的 ; 但不能超过这个。 同样,正如我之前所说的那样,Node发出的情况是,当我们将这些任务作为来自多个源的系统input时,因为与每个连接的线程数或每个连接的进程数相比,同时保持多个连接同时在Node中轻得多系统。
至于setTimeout(...,0)
调用; 有时在耗费时间的任务中rest一下,以允许队列中的呼叫可以要求他们的处理份额。 以不同的方式划分任务可以让你免受这些困扰。 但是,这还不算真正的黑客,它只是事件队列的工作方式。 而且,使用process.nextTick
来达到这个目的要好得多,因为当你使用setTimeout
,计算和检查时间是必要的,而process.nextTick
就是我们真正想要的:“嘿,任务,回到队尾,你已经使用你的份额!“
(2016年更新:Web工作人员正在进入io.js – Node.js fork Node.js v7 – 请参阅下文。)
(2017年更新:Web工作人员不会进入Node.js v7或v8 – 请参阅下文。)
一些澄清
在阅读了上面的答案之后,我想指出的是,Web工作者中没有任何东西违背JavaScript的哲学,特别是Node并发性。 (如果有的话,甚至不会被WHATWG讨论,更不用说在浏览器中实现了)。
您可以将web worker视为asynchronous访问的轻量级微服务。 没有状态是共享的。 没有locking问题存在。 没有阻塞。 没有需要同步。 就像当您使用Node程序中的RESTful服务一样,您不用担心它现在是“multithreading”,因为REST风格的服务与您自己的事件循环不在同一个线程中。 这只是一个单独的服务,您可以asynchronous访问,这是非常重要的。
networking工作者也是如此。 它只是一个API,与在完全独立的上下文中运行的代码进行通信,并且由于严格的asynchronous非阻塞API,它与不同的线程,不同的进程,不同的cgroup,区域,容器或不同的机器是完全不相关的。所有数据都是按值传递的。
事实上,networking工作者在概念上完全适合于Node,很多人并没有意识到,偶然地使用线程,实际上“除了代码之外,所有东西都是并行运行的”,请参阅:
- 通过Mikito Takada 了解node.js事件循环
- 通过FelixGeisendörfer 了解node.js
- 了解 Trevor Norris 的Node.js事件循环
- Node.js本身就是阻塞的,只有它的I / O不被Jeremy Epstein 阻塞
但是networking工作者甚至不需要使用线程来实现。 只要使用web worker API,您就可以在云中使用进程,绿色线程或甚至RESTful服务。 通过值语义调用传递API的消息的完美之处在于底层实现几乎是无关紧要的,因为并发模型的细节不会暴露。
单线程事件循环非常适合I / O绑定操作。 对于CPU绑定的操作,尤其是长时间运行的操作来说,这并不好。 为此,我们需要产生更多的进程或使用线程。 以便携的方式pipe理subprocess和进程间通信可能相当困难,而且通常被认为是简单任务的矫枉过正,而使用线程则意味着处理非常难以正确执行的锁和同步问题。
通常推荐的做法是将长时间运行的CPU绑定操作划分为更小的任务(类似于我的 “ 加速setInterval ”的答案的“原始答案”部分中的示例),但它并不总是实用,并且不会使用更多比一个CPU核心。
我正在写它来澄清那些基本上是说networking工作者是为浏览器而不是服务器创build的评论(忘记它可以说是关于JavaScript的几乎所有东西)。
节点模块
有几个模块应该将Web Worker添加到节点:
我没有使用过其中的任何一个,但我有两个可能相关的快速观察:截至2015年3月,node-webworker最近在4年前更新,node-webworker-threads在一个月前更新。 另外我在节点webworker线程的使用示例中看到,您可以使用函数而不是文件名作为Worker构造函数的参数,如果它使用共享内存的线程实现,似乎可能会导致微妙的问题(除非函数只用于.toString()方法,否则编译在不同的环境中,在这种情况下,它可能是好的 – 我必须更深入地观察它,只是在这里分享我的观察)。
如果在Node中有其他相关项目实现web worker API,请留言。
更新1
在写这篇文章的时候我还不知道,但是在我写这个答案之前的一天, Web Workers被添加到了io.js中 。
( io.js是Node.js的一个分支 – 请参阅: 为什么io.js决定分叉Node.js ,InfoWorld对Mikeal Rogers的采访,以获取更多信息。)
这不仅certificate了networking工作者中没有任何东西违背了JavaScript的哲学,特别是关于并发的Node,而且可能导致web工作者成为像io这样的服务器端JavaScript的头等公民。 js(以及未来可能的Node.js),就像它在所有现代浏览器中的客户端JavaScript一样。
更新2
在更新1和我的推特,我指的是io.js拉请求#1159 ,现在redirect到7月8日closures的节点PR#1159 ,取而代之的是仍然打开的节点PR#2133 。 在这些拉取请求下正在进行一些讨论,可能会提供一些有关io.js / Node.js中Web工作人员状态的最新信息。
更新3
最新的信息 – 感谢NiCk Newman发表评论: 工作人员: 2015年9月6日,佩特卡•安东诺夫(Petka Antonov) 初步实施 ,可以下载并试用这棵树 。 细节见NiCk Newman的评论 。
更新4
截至2016年5月 ,关于仍在开放的PR#2133 – 工作人员的最后评论:最初的实施是3个月大。 5月30日,Matheus Moreira要求我在下面的评论中发布这个答案的更新,他在PR评论中询问了这个特性的当前状态 。
PR讨论中的第一个答案是怀疑的,但后来Ben Noordhuis 写道 :“让这个或那个forms合并在我的待办事项列表v7”。
所有其他评论似乎在第二位,截至2016年7月, Web Workers似乎应该在下一个计划于2016年10月发布的7.0 版本的Node中使用 (不一定是以这种确切的PR的forms)。
感谢Matheus Moreira在评论中指出了这一点,并重新讨论了GitHub。
更新5
截至2016年7月 ,npm上很less有以前没有的模块 – 有关模块的完整列表,工作人员,networking工作人员searchnpm等。如果有任何特别的事情对您有用或不适合,请发布评论。
更新6
截至2017年1月 ,networking工作者不太可能会被合并到Node.js中。
2133号工作人员: Petka Antonov于2015年7月8日初步实施 ,最终于 2016年12月11日由Ben Noordhuis closures ,他评论道:“multithreading支持为不足的收益增加了太多新的失败模式”,“我们也可以使用更传统的手段,如共享内存和更高效的序列化。“
欲了解更多信息,请参阅GitHub PR 2133的评论。
再次感谢Matheus Moreira在评论中指出。
我来自我们使用multithreading来快速制作软件的旧思想。 过去三年来,我一直在使用Node.js和它的一个大支持者。 正如hasanyasin详细解释了节点如何工作以及asynchronousfunction的概念。 但是让我在这里添加一些东西。
回到过去,单核和更低的时钟速度,我们尝试了各种方法来使软件快速并行地工作。 在DOS时代,我们使用一次运行一个程序。 与Windows相比,我们开始一起运行多个应用程序(进程)。 像抢先和非抢先(或合作)的概念进行testing。 我们现在知道,先发制人是单核计算机上更好的多处理任务的答案。 一起来了进程/任务和上下文切换的概念。 比线程的概念更进一步减轻了进程上下文切换的负担。 线程在那里创造轻量级替代产卵新的进程。
所以喜欢它或不是信号线程或不是多核心或单核你的进程将被抢占和时间分割的操作系统。
Nodejs是一个单独的进程,并提供asynchronous机制。 在这里,作业被调度到操作系统下执行任务,而我们在任务完成的事件循环中等待。 一旦我们从操作系统得到一个绿色信号,我们将执行我们需要做的事情。 从某种意义上说,这是一种合作式/非抢先式的多任务处理方式,所以我们不应该在很长一段时间内阻塞事件循环,否则我们会非常快地降低应用程序的性能。
因此,如果任何一项任务本质上是阻塞的,或者是非常耗时的,我们将不得不将其分解到OS和线程的抢先世界。 有很好的例子是libuv文档 。 此外,如果您进一步阅读文档,则会发现在node.js中的线程中处理了FileI / O。
所以首先它的一切都在我们的软件的devise中。 其次上下文切换总是发生,不pipe他们告诉你什么。 线程在那里,还有一个原因,原因是他们更快地切换之间的进程。
在node.js中引用所有的c ++和线程。 而且节点提供了c ++的方式来扩展它的function,并通过使用线程来进一步加速它们是必须的,也就是阻塞任务,比如从源文件读到源文件,大数据分析等等。
我知道hasanyasin答案是可以接受的,但是对于我来说,无论你说什么或者如何将它们隐藏在脚本之后,线程都将存在,其次,没有人为了加速而将事情分解到线程中,主要是为了阻止任务。 线程在Node.js的后面,所以在完全抨击multithreading之前是正确的。 线程也不同于进程,每个核心节点进程的限制并不完全适用于线程数,线程就像进程的子任务。 实际上线程赢了; t显示在你的Windows任务pipe理器或Linux顶部的命令。 再次,他们是更轻量级的过程
在这种情况下,我不确定webworkers是否相关,它们是客户端技术(在浏览器中运行),而node.js在服务器上运行。 据我所知,纤维也是阻塞的,即它们是自愿的多任务处理,所以你可以使用它们,但是应该通过yield
来pipe理上下文。 线程可能实际上是你所需要的,但我不知道它们在node.js中有多成熟。
在许多Node开发者的意见中,Node的最好的部分之一实际上是它的单线程性质。 线程引入了共享资源的困难,Node完全避免了无阻塞的IO。
这并不是说Node只限于一个线程。 只是线程并发的方法不同于你正在寻找的方法。 处理线程的标准方法是使用Node自身标准的集群模块。 线程比线程更简单,而不是在代码中手动处理它们。
为了处理代码中的asynchronous编程(如避免嵌套callback金字塔), Fibers库中的[Future]组件是一个不错的select。 我也build议你看看基于Fibers的Asyncblock 。 纤维很好,因为它们允许你通过复制堆栈来隐藏callback,然后在需要的时候在单线程之间跳转。 给您带来好处的同时,为您节省真正的麻烦。 不足之处在于,使用Fibers时,堆栈跟踪可能会有些奇怪,但它们并不算太坏。
如果你不需要担心asynchronous的东西,而且更愿意做大量没有阻塞的处理,那么每隔一段时间简单地调用一次process.nextTick(callback函数)就足够了。
也许关于你正在执行什么任务的更多信息会有所帮助。 为什么你需要(正如你在genericdave的回答中提到的那样)需要创build数千个呢? 在Node中做这种事情的通常方法是启动一个工作进程(使用fork或其他方法),它始终运行,并可以使用消息传递给进程。 换句话说,每次你需要执行你正在做的任何任务时,不要启动一个新的工作者,而只需要发送一个消息给正在运行的工作者,并在完成时得到响应。 老实说,我看不到开始数以千计的实际线程将是非常有效的,你仍然受限于你的CPU。
现在,在说了这么多之后,我最近在Hook.io上做了很多工作,对于这种卸载任务在其他进程中似乎工作得很好,也许可以完成你所需要的工作。