sjt*_*tus 6 javascript multithreading asynchronous node.js async-await
我对Node js的架构和性能有疑问.
我已经完成了一些关于这个主题的阅读(包括Stack Overflow),我还有几个问题.我想做两件事:
Node具有单线程,异步事件处理架构
单线程 - 有一个事件线程可以调度异步工作(结果通常是I/O但可以是计算)并执行回调执行(即处理异步工作结果).
事件线程在无限的"事件循环"中运行,执行上述2个作业; a)通过调度异步工作来处理请求,以及b)注意先前的异步工作结果已准备好并执行回调以处理结果.
这里的常见类比是餐厅订单接收者:活动线程是一个超级快速的服务员,从餐厅接受订单(服务请求)并将订单发送到厨房准备(发送异步工作),但也注意到当食物准备就绪(异步结果)并将其送回表格(回调执行).
服务员不做任何食物; 他的工作是尽快从餐厅到厨房来回走动.如果他陷入餐厅的订单陷入困境,或者如果他被迫回到厨房准备其中一餐,系统就变得效率低下并且系统吞吐量受损.
异步 由请求(例如Web请求)产生的异步工作流在逻辑上是一个链:例如
FIRST [ASYNC: read a file, figure out what to get from the database] THEN
[ASYNC: query the database] THEN
[format and return the result].
Run Code Online (Sandbox Code Playgroud)
上面标有"ASYNC"的作品是"厨房工作","FIRST []"和"THEN []"代表服务员开始回调的参与.
像这样的链以3种常见方式以编程方式表示:
嵌套函数/回调
用.then()链接的承诺
async()异步结果的异步方法.
所有这些编码方法都非常相同,尽管asynch/await似乎是最干净的,并且使得异步编码的推理更容易.
问题
我的问题涉及使用OS支持的异步操作,实际执行异步工作的人,以及这种体系结构比"按每个请求生成一个线程"(即多个cooks)体系结构更高效的方式:
通过使用跨平台异步库libuv设计节点库是异步的,对吗?这里的想法是libuv为节点(在所有平台上)提供一致的异步I/O接口,但随后使用平台相关的异步I/O操作吗?在I/O请求"一直向下"到OS支持的异步操作的情况下,谁正在"做工作"等待I/O返回并触发节点?它是内核,使用内核线程吗?如果不是,谁?无论如何,这个实体可以处理多少个请求?
我已经读过libuv也在内部使用了一个线程池(通常是pthreads,每个核心一个?).这是为了"包装"不像"async"那样"完全向下"的操作,这样一个线程就可以用来等待同步操作了,所以libuv可以提供一个异步API吗?
关于性能,用于解释类似节点的体系结构可以提供的性能提升的通常说明是:画出(可能更慢和更胖)线程每请求方法 - 产生的延迟,CPU和内存开销一堆线程只是坐在等待I/O完成(即使他们没有忙碌等待)然后将它们拆除,节点很大程度上让它消失了,因为它使用了一个长期存在的事件线程来将异步I/O发送到OS /内核,对吗?但是在一天结束的时候,SOMETHING正在睡眠互联网并在I/O准备就绪时被唤醒...是否认为内核比用户线程更有效?最后,请问由libuv的线程池处理的情况怎么样...这似乎与每个请求线程的方法类似,除了使用池的效率(避免启动和拆除),但是在这种情况下,当有很多请求并且池有积压时会发生什么?...延迟增加,现在你做的比每个请求的线程更糟,对吧?
SO 上有很好的答案,可以让您更清楚地了解架构。但是,您有一些可以回答的具体问题。
谁在“做等待 I/O 返回并触发节点的工作”?是内核,使用内核线程吗?如果不是,那是谁?无论如何,该实体可以处理多少个请求?
实际上,线程和异步 I/O 都是在同一个原语之上实现的:操作系统事件队列。
多任务操作系统的发明是为了允许用户使用单个 CPU 核心并行执行多个程序。是的,多核、多线程系统当时确实存在,但它们很大(通常是两到三间普通卧室的大小)并且昂贵(通常是一两间普通房屋的成本)。这些系统可以在没有操作系统帮助的情况下并行执行多个操作。您所需要的只是一个简单的加载程序(称为执行程序,一个原始的类似 DOS 的操作系统),并且您可以直接在汇编中创建线程,而无需操作系统的帮助。
更便宜、更大规模生产的计算机一次只能运行一件事。很长一段时间以来,这都是用户可以接受的。然而,习惯了分时系统的人们希望从他们的计算机中获得更多。因此进程和线程就被发明了。
但在操作系统级别没有线程。操作系统本身提供线程服务(嗯......从技术上讲,您可以将线程作为库实现,而不需要操作系统支持)。那么OS是如何实现线程的呢?
中断。它是所有异步处理的核心。
进程或线程只是等待 CPU 处理并由操作系统管理的事件。这是可能的,因为 CPU 硬件支持中断。任何等待 I/O 事件(来自鼠标、磁盘、网络等)的线程或进程都会被停止、挂起并添加到事件队列中,并且在等待时间内执行其他进程或线程。CPU 中还内置了一个定时器,可以触发中断(令人惊讶的是,该中断被称为定时器中断)。此计时器中断会触发操作系统的进程/线程管理系统,以便您仍然可以并行运行多个进程,即使它们都没有等待 I/O 事件。
这是多任务处理的核心。除了操作系统设计、嵌入式编程(您经常需要在没有操作系统的情况下执行类似操作系统的事情)和实时编程之外,通常不会教授这种编程(使用定时器和中断)。
那么,异步 I/O 和进程之间有什么区别呢?
除了操作系统向程序员公开的 API 之外,它们完全相同:
进程/线程:嘿,程序员,假装您正在为单个 CPU 编写一个简单的程序,并假装您可以完全控制 CPU。来吧,使用我的 I/O。当我处理并行运行的混乱问题时,我会保持你控制 CPU 的错觉。
异步I/O:你认为你比我更了解吗?好的,我让您将事件侦听器直接添加到我的内部队列中。但我不会处理事件发生时调用哪个函数。我只是粗暴地唤醒你的进程,然后你自己处理所有的事情。
在多核 CPU 的现代世界中,操作系统仍然执行这种进程管理,因为典型的现代操作系统运行数十个进程,而 PC 通常只有两个或四个内核。对于多核机器还有另一个区别:
进程/线程:由于我正在为您处理进程队列,我想您不会介意我将您要求我在多个 CPU 上运行的线程的负载分散,好吗?这样我就可以让硬件并行地完成工作。
异步 I/O:抱歉,我无法将所有不同的等待回调分散到不同的 CPU 上,因为我不知道你的代码到底在做什么。单核为您服务!
我读到 libuv 还在内部使用了线程池(通常是 pthreads,每个核心一个?)。这是为了将不“一路向下”的操作“包装”为异步
是的。
实际上,据我所知,所有操作系统都提供了足够好的异步 I/O 接口,因此您不需要线程池。自 80 年代以来,编程语言Tcl一直在不借助线程池的情况下处理类似节点的异步 I/O。但事情却很混乱,并不那么简单。Node 开发人员决定,他们不想在磁盘 I/O 方面处理这种混乱情况,而只是使用经过更充分测试的阻塞文件 API 和线程。
但最终,某些东西正在互斥锁上休眠,并在 I/O 准备就绪时被唤醒
我希望我对(1)的回答也能回答这个问题。但如果你想知道那是什么,我建议你阅读select()C 语言中的函数。如果你了解 C 编程,我建议你尝试使用 .NET 编写一个没有线程的 TCP/IP 程序select()。谷歌“选择c”。我在另一个答案中对这一切在 C 级别如何工作有更详细的解释:我知道回调函数是异步运行的,但为什么呢?
当有很多请求并且池有积压时会发生什么?......延迟增加,现在你的表现比每个请求的线程更糟糕,对吗?
我希望一旦您理解了我对(1)的答案,您也会意识到即使您使用线程也无法摆脱积压。硬件并不真正支持操作系统级线程。硬件线程受限于核心数量,因此在硬件级别,CPU 是一个线程池。单线程和多线程之间的区别很简单,多线程程序实际上可以在硬件中并行执行多个线程,而单线程程序只能使用单个CPU。
异步 I/O 和传统多线程程序之间唯一真正的区别是线程创建延迟。从这个意义上说,像node.js这样的程序比像nginx和apache2这样使用线程池的程序没有优势。
然而,由于 CGI 的工作方式,像 Node.js 这样的程序仍然具有更高的吞吐量,因为您不必为每个请求重新初始化解释器和程序中的所有对象。这就是为什么大多数语言已经转向作为 HTTP 服务运行的 Web 框架(如 Node 的 Express.js)或 FastCGI 之类的东西。
注意:您真的想知道线程创建延迟有什么大不了的吗?在 20 世纪 90 年代末/2000 年代初,出现了一个 Web 服务器基准测试。Tcl 是一种众所周知的平均比 C 慢 500% 的语言(因为它基于像 bash 这样的字符串处理),但却成功地超越了 apache(这是在 apache2 之前的情况,并引发了创建 apache2 的完整重新架构)。原因很简单:tcl 有很好的异步 I/O api,因此程序员更有可能使用异步 I/O。仅此一点就击败了用 C 编写的程序(并不是说 C 没有异步 I/O,毕竟 tcl 是用 C 编写的)。
Node.js 相对于 Java 等语言的核心优势并不在于它具有异步 I/O。异步 I/O 非常普遍,而且 API(回调、Promise)易于使用,因此您可以使用异步 I/O 编写整个程序,而无需使用汇编语言或 C 语言。
如果您认为回调很难使用,我强烈建议您尝试用select()C 编写基于该程序。
| 归档时间: |
|
| 查看次数: |
346 次 |
| 最近记录: |