繁体   English   中英

为什么 IO 的非阻塞异步单线程比某些应用程序的阻塞多线程更快

[英]Why is Non blocking asynchronous single-threaded faster for IO than blocking multi-threaded for some applications

它通过使用现实世界的比较帮助我理解事物,在这种情况下是快餐。

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

在 javascript 你可以有异步非阻塞但单线程。 据我了解,发出了多个请求,这些请求立即被接受,但请求在返回之前的某个时间由某个后台进程处理。 我不明白这怎么会更快。 如果您同时订购 10 个汉堡,则 10 个请求将立即发出,但由于只有一名厨师(单线程),创建 10 个汉堡仍然需要相同的时间。

我的意思是我理解为什么非阻塞异步单线程“应该”更快的推理,但我问自己的问题越多,我就越不理解它,这让我不理解它。

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

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

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

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

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

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

  • 我们正在运行的指令在 memory 中的位置是什么
  • 整个“堆栈”。 如果某个 function 调用第二个 function,那么我们需要记住所有局部变量和我们在原始方法中所处的位置,以便当第二个方法“返回”时,它知道如何做。 例如,您的平均 java 程序可能有 ~20 个方法深度,所以这是本地变量的 20 倍,代码中有 20 个位置需要跟踪。 这都是在堆栈上完成的。 每个线程都有一个。 它们往往是整个应用程序的固定大小。
  • 在运行此代码的核心的本地缓存中启动了哪些缓存页面?

线程中的代码编写如下:所有与“资源”交互的命令(比你的 CPU 慢几个数量级;think.network 数据包、磁盘访问等)被指定为立即返回请求的数据(仅如果您要求的一切都已经可用并且在内存中,则可能)。 如果那是不可能的,因为你想要的数据还不存在(假设携带你想要的数据的数据包仍在线路上,前往你的网卡),那么代码只需要做一件事为“获取我的网络数据”提供动力 function:等待数据包到达并进入 memory。

为了不只是什么都不做,操作系统/CPU 将协同工作以获取代表线程的数据结构,冻结它,找到另一个这样的冻结数据结构,解冻它,然后跳转到“我们把东西放在哪里”点代码。

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

线程切换涉及移动一堆 memory:所有那些“实时”缓存页面和那个堆栈,需要靠近那个核心才能让 CPU 完成工作,所以这是一个 CPU 从 main memory 加载一堆页面,这确实需要一些时间。 不是很多(纳秒),但也不为零。 现代 CPU 只能对附近缓存页中加载的数据进行操作(大小约为 64k 到 1MB,仅此而已,比您的 RAM 棒可以存储的内容少一千倍以上)。

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

当然还有一个线程(所有事情都在一个运行中),但是这次有问题的应用程序根本不是多线程的。 相反,它本身创建了跟踪多个传入连接所需的数据结构,而且至关重要的是,用于请求数据的原语工作方式不同。 请记住,在同步情况下,如果代码从 .network 连接请求下一堆字节,那么线程将“冻结”(告诉 kernel 找到其他工作要做),直到数据在那里。 在异步模式下,如果可用则返回数据,但如果不可用,function“给我一些数据”仍然返回:但它只是说。 对不起芽。 我有 0 个新字节给你。

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

请注意,如果数据到达,比如说,连接#5,那么这个线程,为了完成处理这个传入数据的工作,可能需要从 memory 加载一堆 state 信息,并且可能需要写入它.

例如,假设您正在处理一幅图像,一半的 PNG 数据在线上传输。 您不能用它做很多事情,所以这个线程将创建一个缓冲区并将一半的 PNG 存储在其中。 当它跳到另一个连接时,它需要加载它已经获得的图像的 ~15%,并将刚到达网络数据包中的图像的 10% 添加到该缓冲区。

这个应用程序还导致一堆 memory 被移入和移出缓存页面,所以在这个意义上它并没有那么不同,如果你想一次处理 100k 的东西,你不可避免地会最终不得不将内容移入和移出缓存页面。

那么区别是什么呢? 你能用油炸厨师的话来说吗?

不是真的,不。 这只是数据结构。

关键区别在于哪些内容被移入和移出这些缓存页面。

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

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

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

如果这些传入图像的大小真的只有 4k 左右,那么您可以使用 4k 缓冲区来完成它,如果您通过异步手动处理,则最多只需要 0.4GB 的 memory。

这就是 async 的好处所在:通过手动滚动缓冲区,您无法避免将 memory 移入和移出缓存页面,但可以确保它是更小的块。 那会更快。

当然,为了真正让它更快,在异步 model 中存储 state 的缓冲区需要很小(如果你需要在操作之前将 128k 保存到 memory 中,这没什么意义,这就是那些堆栈已经有多大了),并且您需要一次处理很多事情(同时处理 10k+)。

我们没有用汇编程序编写所有代码或者 memory 托管语言流行的原因是:处理此类问题既乏味又容易出错。 除非好处很明显,否则您不应该这样做。

这就是为什么同步通常是更好的选择,并且在实践中通常实际上更快(那些操作系统线程调度程序是由专家编码人员编写的并且调整得非常好。你没有机会复制他们的工作) - 整个'通过手推我的缓冲区我可以减少需要移动一吨的字节数。 事情需要大于损失。

另外,async 编程复杂 model。

在异步模式下,你永远不能阻塞。 想做一个快速的数据库查询? 这可能会阻塞,所以你不能那样做,你必须将代码编写为: 好的,启动这个作业,这里有一些代码在它返回时运行。 你不能“等待答案”,因为在异步领域,等待是不允许的。

在异步模式下,无论何时你请求数据,你都需要能够处理只得到你想要的一半的数据。 在同步模式下,如果你要求 4k,你就会得到 4k。 事实上,您的线程可能会在此任务期间冻结,直到 4k 可用,这不是您需要担心的事情,您编写代码就像它刚好在您要求时到达一样,完成了。

Bbbuutt...炒厨师!

看,CPU 设计还不够简单,无法用这样的餐厅来表示。

您正在将瓶颈从您的流程(汉堡订购者)转移到另一个流程(汉堡制造商)。

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

考虑单线程异步 model 时,真正的好处是您的进程在等待其他进程时不会被阻塞

换句话说,不要将 async 与fast相关联,而是与free相关联。 有空做其他工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM