为什么对于IO,非阻塞异步单线程比在某些应用程序中阻塞多线程更快?

时间:2020-08-19 15:04:51

标签: javascript java asynchronous nonblocking synchronous

它通过使用现实世界的比较(在本例中为快餐)来帮助我理解事物。

在Java中,对于同步阻塞,我了解到一个线程处理的每个请求一次只能完成一个。就像通过开车订购一样,所以如果我排在第十名,我必须等我前面的9辆车。但是,我可以打开更多线程,以便同时完成多个订单。

在javascript中,您可以具有异步非阻塞但具有单线程的功能。据我了解,会发出多个请求,并且这些请求会立即被接受,但是在返回之前的某个稍后时间,该请求会由某些后台进程处理。我不知道这样会更快。如果您同时订购10个汉堡,则会立即提出10个请求,但是由于只有一名厨师(单线程),创建10个汉堡仍然需要花费相同的时间。

我的意思是我理解为什么非阻塞异步单线程“应该”更快地处理某些事情的原因,但是我问自己的问题越多,我对它的理解就越少,这会使我不明白。

我真的不明白,对于包括IO在内的任何类型的应用程序,非阻塞异步单线程如何比同步阻塞多线程更快?

2 个答案:

答案 0 :(得分:2)

非阻塞异步单线程有时会更快

这不太可能。你从哪里得到的?

在多线程同步I / O中,大致是这样的:

OS和Appserver平台(例如JVM)共同创建10个线程。这些是内存中表示的数据结构,运行在内核/操作系统级别的调度程序将使用这些数据结构来告诉您的一个CPU内核“跳到”代码中的某个点以运行在其中找到的命令。

表示线程的数据结构或多或少包含以下各项:

  • 我们正在运行的指令在内存中的位置是什么
  • 整个“堆栈”。如果某个函数调用了第二个函数,那么我们需要记住所有局部变量以及该原始方法中的要点,以便当第二个方法“返回”时,它知道如何执行此操作。例如您的平均Java程序大概有20种方法,因此,这是本地变量的20倍,在代码中要跟踪20个位置。这些都是在堆栈上完成的。每个线程都有一个。它们在整个应用中的大小通常是固定的。
  • 运行此代码的核心的本地缓存中弹出了哪些缓存页面?

该线程中的代码编写如下:与“资源”交互的所有命令(比您的CPU慢几个数量级;认为网络数据包,磁盘访问等)被指定为立即返回所请求的数据(仅当您要求的所有内容已经可用并在内存中时才可能)。如果那是不可能的,因为所需的数据还不存在(例如,承载所需数据的数据包仍在网上,一直指向您的网卡),那么执行代码的功能只有一件事“获取我的网络数据”功能:等待该数据包到达并进入内存。

为了不做任何事情,OS / CPU将协同工作以获取表示线程的数据结构,将其冻结,找到另一个这样的冻结数据结构,将其解冻,然后跳转到“我们在哪里放置东西”点代码。

那是一个“线程切换”:核心A正在运行线程1。现在核心A正在运行线程2。

线程切换涉及到移动一堆内存:所有那些“活动的”缓存页面和该堆栈都需要位于该核心附近,以使CPU能够完成工作,因此这是CPU在一堆页面中加载的从主内存中,这确实需要一些时间。不是很多(纳秒),但也不是零。现代CPU只能对附近缓存页中加载的数据进行操作(大小约为64k到1MB,不超过此大小,比RAM棒可以存储的大小少一千倍以上)。

在单线程异步I / O中,大致是这样的:

当然仍然有一个线程(所有事物都在一个线程中运行),但是这次有问题的应用程序根本没有多线程。相反,它本身会创建跟踪多个传入连接所需的数据结构,并且至关重要的是,用于请求数据的原语以不同的方式工作。请记住,在同步情况下,如果代码要求从网络连接中获取下一个字节,则线程将最终“冻结”(告诉内核以查找其他工作),直到数据存在为止。在异步模式下,如果可用,则返回数据,但是如果不可用,则函数“给我一些数据!”仍然返回,但只说:对不起,芽。我有0个新字节供您使用。

然后,应用程序本身将决定在其他某个连接上工作,这样,一个线程就可以管理一堆连接:是否有用于连接#1的数据?是的,太好了,我将处理。没有?哦好的。连接2是否有数据?等等等等。

请注意,如果数据到达(例如,连接#5),则此线程要执行处理此传入数据的工作,可能需要从内存中加载一堆状态信息,并且可能需要来写。

例如,假设您正在处理图像,并且一半的PNG数据通过网络到达。您可以用它做很多事情,因此,这个线程将创建一个缓冲区,并将一半的PNG存储在其中。然后,当它跳到另一个连接时,它需要加载它获得的大约15%的图像,并将刚到达网络数据包的10%的图像添加到该缓冲区中。

此应用程序还导致一堆内存完全相同地移入和移出缓存页面,因此从某种意义上讲,并没有什么不同,如果您要处理10万个事情一旦发生,您将不可避免地不得不将内容移入和移出缓存页面。

那有什么区别?可以用炒菜的方式放吗?

不是,不是。都是数据结构。

主要区别在于哪些内容移入和移出了这些缓存页面。

在异步情况下,这正是您编写的代码想要缓冲的内容。不多不少。

在同步的情况下,就是“代表线程的数据结构”。

以java为例:这至少意味着该线程的整个堆栈。也就是说,根据-Xss参数,大约需要128k的数据。因此,如果您要同时处理10万个连接,那么对于那些堆栈来说,这就是12.8GB的RAM just

如果这些传入的图像的大小实际上仅为4k,那么您可以通过4k缓冲区来完成它,如果您通过异步操作将其最多存储0.4GB的内存。

这就是异步的好处:通过滚动缓冲区,您无法避免将内存移入和移出缓存页面,但是可以确保内存块较小。这样会更快。

当然,要真正使其更快,异步模型中用于存储状态的缓冲区必须很小(如果您需要先将128k保存到内存中然后再对其进行操作,则无需过多说明这一点,即堆栈),并且您需要一次处理很多事情(同时处理10k +)。

有一个原因是我们没有用汇编器编写所有代码,或者是为什么内存管理的语言流行:手工滚动这样的问题既乏味又容易出错。除非好处明确,否则您不应该这样做。

这就是为什么同步通常是更好的选择,而实际上通常通常更快(这些OS线程调度程序是由专家编码员编写的,并且进行了很好的调整。您没有机会复制他们的工作)-整个“通过滚动缓冲区,我可以减少需要移动一吨左右的字节数!”事情要大于损失。

此外,异步作为一种编程模型非常复杂。

在异步模式下,您永远无法阻止。想做一个快速的数据库查询吗?那可能会阻塞,所以您不能这样做,您必须将代码编写为:好的,解雇此作业,这里有一些代码在返回时要运行。您不能“等待答案”,因为在异步土地上,不允许等待。

在异步模式下,无论何时您要求数据,您都必须能够处理所需内容的一半。在同步模式下,如果要求4k,则得到4k。不必担心线程会在此任务期间冻结直到4k可用,您无需担心,您可以编写代码,就好像它随您的要求而到达一样。

Bbbuutt ...炒菜!

看起来,CPU设计还不够简单,无法像这样的餐厅。

答案 1 :(得分:0)

您正在从瓶颈(汉堡订购者)到其他过程(<汉堡>制造商)转移瓶颈。

这不会使您的应用程序更快。

在考虑单线程异步模型时,真正的好处是在等待另一个进程的同时不阻塞您的进程。

换句话说,请勿将异步与快速关联,而应与免费关联。免费做其他工作。

相关问题