我绊倒在地.一段时间以前,我很喜欢它.但很快我发现它严重缺乏执行CPU密集型任务的能力.于是,我开始用谷歌搜索,找到了解决问题的答案:纤维、网络工作者和线程(thread-a-gogo).现在使用哪一个是一个困惑,其中一个肯定需要使用——毕竟,拥有一个只擅长IO而不擅长其他功能的服务器的目的是什么?需要建议!

UPDATE:

我在想一条晚点离开的路;只是需要一些建议.现在,我想到的是:让我们有一些线程(使用thread_a_gogo或者webworkers).现在,当我们需要更多的时候,我们可以创造更多.但创作过程会有一些限制.(系统没有暗示,但可能是因为开销).现在,当我们超过限制时,我们可以Forking 一个新 node ,并开始在其上创建线程.这样,它可以一直持续到我们达到某个极限(毕竟,进程也有很大的开销).当达到这个限制时,我们开始排队处理任务.每当一个线程空闲时,它将被分配一个新任务.这样就可以顺利进行.

我就是这么想的.这个主意好吗?我对所有这些过程和线程的东西都有点陌生,所以我在这方面没有任何专业知识.请分享你的观点.

谢谢.:)

推荐答案

Node有一个完全不同的范例,一旦它被正确捕获,就更容易看到这种解决问题的不同方式.在一个 node 应用程序中永远不需要多个线程(1),因为做同一件事的方式不同.创建多个流程;但它与ApacheWeb服务器的Prefork mpm非常不同.

现在,让我们假设我们只有一个CPU核心,我们将开发一个应用程序(以 node 的方式)来做一些工作.我们的工作是逐字节处理一个大文件的内容.对于我们的软件来说,最好的方法是从文件的开头开始工作,一个字节一个字节地跟踪到最后.

--嘿,哈桑,我想你要么是个新手,要么是我祖父时代的老派!!!为什么不创建一些线程并使其更快呢?

--哦,我们只有一个CPU核心.

--那又怎样?创建一些线程,让它更快!

--它不是那样工作的.如果我创建线程,我会让它变慢.因为我会给系统增加很多开销,以便在线程之间切换,给它们一点时间,在我的进程中,try 在这些线程之间进行通信.除了所有这些事实,我还必须考虑如何将一项工作划分为多个可以并行完成的部分.

--好吧,我看你很穷.让我们用我的电脑吧,它有32个核!

--哇,你真棒,我亲爱的朋友,非常感谢.我很感激!

然后我们再回go 工作.多亏了我们富有的朋友,现在我们有了32个cpu核心.我们必须遵守的规则刚刚改变.现在我们想利用我们得到的所有财富.

要使用多核,我们需要找到一种方法,将我们的工作划分为可以并行处理的部分.如果不是 node ,我们将使用线程来实现这一点;32个线程,每个cpu核心一个.然而,由于我们有 node ,我们将创建32个 node 进程.

线程可以是 node 进程的一个很好的替代方案,甚至可能是更好的方式;但只有在一种特定的工作中,工作已经确定,我们完全可以控制如何处理它.除此之外,对于所有其他类型的问题,如果工作来自外部,而我们无法控制,并且希望尽快回答,那么Node的方式无疑是优越的.

--嘿,哈桑,你还在用单线程吗?你怎么了,伙计?我刚刚给你提供了你想要的.你再也没有借口了.创建线程,使其运行更快.

--我已经把作品分成了几个部分,每个过程都会在其中一个部分上并行工作.

--为什么不创建线程呢?

--对不起,我认为它不可用.如果你愿意,你可以带上你的电脑?

--不,好吧,我很酷,我只是不明白你为什么不使用线程?

--谢谢你的电脑.:)我已经把工作分成了几个部分,我创建了并行处理这些部分的流程.所有的CPU核心都将得到充分利用.我可以用线程而不是进程来实现这一点;但Node有这种方式,我的老板Parth Thakkar希望我使用Node.

--好的,如果你需要另一台电脑,请告诉我P

如果我创建33个进程,而不是32个,操作系统的调度程序将暂停一个线程,启动另一个,在一些周期后暂停,再次启动另一个...这是不必要的开销.我不要.事实上,在一个32核的系统上,我甚至不想创建32个进程,31个可以是nicer个.因为在这个系统上工作的不仅仅是我的应用程序.给其他东西留一点空间是很好的,尤其是如果我们有32个房间的话.

我相信我们现在在CPU-intensive tasks个处理器的充分利用上是一致的.

--哈桑,我很抱歉嘲笑了你一点.我相信我现在更了解你了.但我仍然需要一个解释:运行数百个线程的所有嗡嗡声是什么?我到处都读到,线程的创建速度要比Forking 进程快得多,而且很愚蠢?您Forking 进程而不是线程,并且您认为这是使用Node可以获得的最高值.那么Node不适合这种工作吗?

--别担心,我也很酷.每个人都说这些话,所以我想我已经习惯了.

--所以呢?Node不适合这个?

--Node在这方面非常好,尽管线程也可以很好.至于线程/进程创建开销;对于你经常重复的事情,每毫秒都很重要.然而,我只创建了32个进程,只需要很少的时间.它只会发生一次.这不会有任何区别.

--那么,我想什么时候创建数千个线程呢?

--你永远不想创建成千上万的线程.然而,在一个正在做来自外部的工作的系统上,比如处理HTTP请求的web服务器;如果您对每个请求使用一个线程,您将创建很多线程,其中很多.

--但 node 不同吗?正当

--没错.这个 node 真正发光的地方.就像线程比进程轻得多一样,函数调用也比线程轻得多. node 调用函数,而不是创建线程.在web服务器的示例中,每个传入的请求都会导致函数调用.

--嗯,很有趣;但如果不使用多个线程,则只能同时运行一个函数.当大量请求同时到达web服务器时,这怎么可能起作用?

--关于函数如何运行,您完全正确,一次一个,从来没有两个函数并行运行.我的意思是,在一个进程中,一次只运行一个代码范围.OS调度器不会来暂停此函数并切换到另一个函数,除非它暂停进程以给另一个进程(而不是进程中的另一个线程)留出时间.(2)

--那么一个进程如何一次处理两个请求呢?

--只要我们的系统有足够的资源(RAM、网络等),一个进程一次可以处理数万个请求.这些功能的运行方式是关键区别.

--嗯,我现在应该兴奋吗?

--可能:) node 在队列上运行循环.在这个队列中是我们的工作,即我们开始处理传入请求的呼叫.这里最重要的一点是我们设计函数运行的方式.我们没有开始处理请求,让调用者等到我们完成任务,而是在完成了可接受的工作量后迅速结束我们的函数.当我们需要等待另一个组件完成一些工作并返回一个值时,我们不需要等待,只需完成将剩余工作添加到队列中的函数.

--听起来太复杂了?

--不,不,我可能听起来很复杂;但这个系统本身非常简单,非常合理.

现在我不想再引用这两个开发者之间的对话了,我想在最后一个简单的例子之后完成我的回答.

通过这种方式,我们正在做OS调度器通常会做的事情.我们在某个时刻暂停工作,让其他函数调用(如多线程环境中的其他线程)运行,直到再次轮到我们为止.这比把工作交给OS调度器要好得多,后者试图给系统上的每个线程留出时间.我们比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与多线程开发有着完全不同的范式;但这并不意味着它缺少东西.对于同步作业(job)(我们可以决定处理的顺序和方式),它与多线程并行性一样有效.对于一个来自外部的工作,比如对服务器的请求,它就更优越了.


(1) 除非您正在用C/C++等其他语言构建库,否则在这种情况下,您仍然不会创建用于分割作业(job)的线程.对于这类工作,您有两个线程,其中一个线程将继续与Node通信,而另一个线程执行实际工作.

(2) 事实上,每个 node 进程都有多个线程,原因与我在第一个脚注中提到的相同.然而,这并不像1000个线程做类似的工作.这些额外的线程用于接受IO事件和处理进程间消息传递.

更新(作为对 comments 中一个好问题的回复)

@马克,谢谢你的建设性批评.在Node的范例中,除非队列中的所有其他调用都被设计为一个接一个地运行,否则永远不应该有处理时间过长的函数.对于计算成本很高的任务,如果我们完整地查看图片,我们会发现这不是"我们应该使用线程还是进程"的问题但问题是"我们如何以一种平衡的方式将这些任务划分为子任务,以便在系统上使用多个CPU核并行运行它们?"假设我们将在一个8核的系统上处理400个视频文件.如果我们想一次处理一个文件,那么我们需要一个系统来处理同一文件的不同部分.在这种情况下,多线程单进程系统可能更容易构建,甚至更高效.我们仍然可以通过运行多个进程并在需要状态共享/通信时在它们之间传递消息来使用Node.如前所述,在此类任务中, node 为as well as的多进程方法是多线程方法;但仅此而已.同样,正如我之前所说的,当我们将这些任务作为输入从多个源输入到系统中时, node 的优势在于,与每个连接的线程或每个连接的进程相比,在 node 中同时保持多个连接要轻得多.

至于setTimeout(...,0)个电话;有时,在一项耗时的任务中,可能需要休息一下,以便让队列中的呼叫有自己的处理份额.以不同的方式划分任务可以让你免于这些;不过,这并不是真正的黑客行为,这只是事件队列的工作方式.此外,使用process.nextTick实现这一目标要好得多,因为当您使用setTimeout时,需要计算和判断经过的时间,而process.nextTick只是我们真正想要的:"嘿,任务,回到队列的末尾,您已经使用了您的份额!"

Node.js相关问答推荐

MongoDB-如何验证Document字段以仅允许特定的文件扩展名?

如何使用Node.js、Express和Mongoose创建多个API

为什么我的表单数据在我的POST请求中作为应用程序/json发送,为什么它返回错误请求错误?

Firebase-admin筛选器.或只考虑第一个WHERE子句

NPM:一般的npm二进制依赖可以静态构建吗?

我的Node.js应用程序没有将Mongoose方法findByIdAndDelete作为函数进行检测

模块';"; node :流程&没有导出的成员';dlopen';

在编译时强制不缩小类型范围

Socket.io 未将用户加入给定房间

SvelteKit应用程序立即退出,没有错误

如何使用包含条件正确分页的 sequelize 查询?

Mongodb 从文档中获取聚合结果中的特定属性

Mongoose,如何一次更新多个?

我们如何或可以通过 npm 和 Meteor 使用 node 模块?

TypeError:winston.Logger 不是带有winston 和morgan 的构造函数

我可以有条件地将 where() 子句添加到我的 knex 查询吗?

从 zip 文件在 AWS 中创建 lambda 函数

找不到在 docker compose 环境中运行的 node js 应用程序的模块

Firestore:多个条件 where 子句

如何阻止 babel 将this转换为undefined(并插入use strict)