IT-Swarm.Net

非阻塞I/O真的比多线程阻塞I/O快吗?怎么样?

我在网上搜索了一些关于阻止I/O和非阻塞I/O的技术细节,我发现有几个人说非阻塞I/O比阻塞I/O更快。例如在 这个文件 。.

如果我使用阻塞I/O,那么当然阻塞的线程当然不会做任何其他事情......因为它被阻止了。但是一旦线程开始被阻塞,操作系统就可以切换到另一个线程而不会切换回来,直到阻塞的线程有事情要做。因此,只要系统上有另一个需要CPU并且没有被阻塞的线程,与基于事件的非阻塞方法相比,不应该有更多的CPU空闲时间,是吗?

除了减少CPU空闲的时间外,我还看到了另外一个选项,可以增加计算机在给定时间范围内可以执行的任务数量:减少切换线程带来的开销。但是怎么做呢?并且开销是否足以显示可衡量的影响?以下是关于如何将其工作的想法:

  1. 要加载文件的内容,应用程序将此任务委托给基于事件的i/o框架,并传递回调函数和文件名
  2. 事件框架委托给操作系统,该操作系统对硬盘的DMA控制器进行编程,将文件直接写入内存
  3. 事件框架允许运行更多代码。.
  4. 完成磁盘到内存复制后,DMA控制器会产生中断。.
  5. 操作系统的中断处理程序通知基于事件的i/o框架有关文件被完全加载到内存中的信息。它是如何做到的?使用信号??
  6. 当前在事件I/O框架内运行的代码完成。.
  7. 基于事件的i/o框架检查其队列并从步骤5中查看操作系统的消息,并执行它在步骤1中获得的回调。.

它是如何工作的?如果没有,它是如何工作的?这意味着事件系统可以在不需要显式触摸堆栈的情况下工作(例如需要备份堆栈并在切换线程时将另一个线程的堆栈复制到内存中的真实调度程序)?这实际节省了多少时间?还有更多吗?

104
yankee

非阻塞或异步I/O的最大优点是您的线程可以并行地继续它的工作。当然,您也可以使用额外的线程来实现这一点。正如您所说的最佳整体(系统)性能,我想最好使用异步I/O而不是多线程(因此减少线程切换)。.

让我们看一下网络服务器程序的可能实现,该程序将处理并行连接的1000个客户端:

  1. 每个连接一个线程(可以阻塞I/O,但也可以是非阻塞I/O)。
    每个线程都需要内存资源(也是内核内存!),这是一个缺点。每个额外的线程意味着调度程序的工作量也会增加。.
  2. 所有连接都有一个线程。
    这会从系统中获取负载,因为我们的线程较少。但它也会阻止您使用机器的全部性能,因为您可能最终将一个处理器驱动到100%并让所有其他处理器闲置。.
  3. 每个线程处理一些连接的几个线程。
    这会从系统中获取负载,因为线程较少。它可以使用所有可用的处理器。在Windows上,这种方法由 Thread Pool API 支持。.

当然,拥有更多线程本身不是问题。您可能已经认识到我选择了相当多的连接/线程。我怀疑如果我们只讨论十几个线程,你会发现三种可能的实现之间有任何区别(这也是Raymond Chen在MSDN博客文章中建议的内容 Windows是否每个进程限制2000个线程? )。.

在Windows上使用 无缓冲文件I/O 表示写入的大小必须是页面大小的倍数。我还没有对它进行过测试,但听起来这也会对缓冲的同步和异步写入产生积极的写入性能影响。.

您描述的步骤1到7可以很好地了解它的工作原理。在Windows上,操作系统将使用事件或回调通知您有关异步I/O(带有WriteFile结构的OVERLAPPED)的完成情况。只有在代码调用WaitForMultipleObjectsExbAlertable设置为true时才会调用回调函数。.

在网上阅读更多内容:

40
Werner Henze

快速,可靠且价格合理的云托管

注册并在30天内获得$50奖金!

I/O包括多种操作,如从硬盘驱动器读取和写入数据,访问网络资源,调用Web服务或从数据库检索数据。根据平台和操作类型,异步I/O通常会利用任何硬件或低级系统支持来执行操作。这意味着它将在CPU上以尽可能小的影响执行。.

在应用程序级别,异步I/O可防止线程必须等待I/O操作完成。一旦启动异步I/O操作,它就会释放启动它的线程并注册回调。操作完成后,回调将排队等待在第一个可用线程上执行。.

如果I/O操作是同步执行的,它会保持正在运行的线程不执行任何操作,直到操作完成。运行时不知道I/O操作何时完成,因此它会定期为等待线程提供一些CPU时间,CPU时间可能由具有实际CPU绑定操作的其他线程使用。.

因此,正如@ user1629468所提到的,异步I/O不提供更好的性能,而是更好的可伸缩性。在具有有限数量的线程可用的上下文中运行时,这是显而易见的,就像Web应用程序的情况一样。 Web应用程序通常使用一个线程池,从中为每个请求分配线程。如果在长时间运行的I/O操作中阻止请求,则存在耗尽Web池并使Web应用程序冻结或响应缓慢的风险。.

我注意到的一件事是,在处理非常快速的I/O操作时,异步I/O不是最佳选择。在这种情况下,在等待I/O操作完成时不保持线程忙的好处不是很重要,并且操作在一个线程上启动并且在另一个线程上完成的事实增加了整个执行的开销。.

您可以阅读我最近就异步I/O与多线程 这里 的主题进行的更详细的研究。.

27
Florin Dumitrescu

使用AIO的主​​要原因是可扩展性。从几个线程的角度来看,好处并不明显。但是当系统扩展到1000个线程时,AIO将提供更好的性能。需要注意的是,AIO库不应该引入进一步的瓶颈。.

4
fissurezone

假设由于任何形式的多计算而导致速度提高,您必须假定多个基于CPU的任务正在多个计算资源(通常是处理器核心)上同时执行,或者并非所有任务都依赖于并发使用相同的资源 - 也就是说,某些任务可能依赖于一个系统子组件(例如磁盘存储),而某些任务依赖于另一个(从外围设备接收通信),还有一些任务可能需要使用处理器核心。.

第一种情况通常被称为“并行”编程。第二种情况通常被称为“并发”或“异步”编程,尽管“并发”有时也用于指代仅允许操作系统交错执行多个任务的情况,无论这种执行是否必须采取串行放置或者如果可以使用多个资源来实现并行执行。在后一种情况下,“并发”通常是指在程序中编写执行的方式,而不是从任务执行的实际同时性的角度。.

用默认的假设来谈论所有这些都很容易。例如,有些人很快就提出了诸如“异步I/O将比多线程I/O更快”的说法。由于几个原因,这种说法是可疑的。首先,可能是某些给定的异步I/O框架精确地使用多线程实现的情况,在这种情况下它们是同一个,并且说一个概念“比另一个更快”没有意义。.

其次,即使在存在异步框架的单线程实现(例如单线程事件循环)的情况下,您仍然必须假设该循环正在做什么。例如,您可以使用单线程事件循环执行的一件愚蠢的事情是请求它异步完成两个不同的纯CPU绑定任务。如果您在仅具有理想化单处理器核心的计算机上执行此操作(忽略现代硬件优化),那么“异步”执行此任务与使用两个独立管理的线程执行此任务或仅使用一个单独的进程执行任务不同 - 差异可能归结为线程上下文切换或操作系统调度优化,但如果两个任务都进入CPU,则两种情况都类似。.

想象一下你可能遇到的很多不寻常或愚蠢的角落情况是很有用的。.

“异步”不必是并发的,例如,如上所述:您在具有一个处理器核心的机器上“异步”执行两个CPU绑定任务。.

多线程执行不必是并发的:您在具有单个处理器核心的机器上生成两个线程,或者要求两个线程获取任何其他类型的稀缺资源(想象一下,例如,只能建立一个的网络数据库)一次连接)。线程的执行可能是 interleaved 然而操作系统调度程序认为合适,但是它们的总运行时间不能在单个核心上减少(并且将从线程上下文切换中增加)(或者更一般地,如果你产生的线程比运行它们的核心多,或者有多个线程要求资源而不是资源可以支持的线程。同样的事情也适用于多处理。.

因此,异步I/O和多线程都不能在运行时方面提供任何性能提升。他们甚至可以放慢速度。.

但是,如果您定义一个特定的用例,就像一个特定的程序,它既可以进行网络调用,从远程数据库等网络连接资源中检索数据,也可以进行一些本地CPU绑定计算,那么您可以开始推理给出了关于硬件的特定假设,两种方法之间的性能差异。.

要问的问题:我需要执行多少计算步骤以及有多少独立的资源系统来执行它们?是否存在需要使用独立系统子组件的计算步骤的子集,并且可以同时从中受益?我有多少处理器核心以及使用多个处理器或线程在不同核心上完成任务的开销是多少?

如果您的任务主要依赖于独立的子系统,那么异步解决方案可能会很好。如果处理它所需的线程数量很大,那么上下文切换对操作系统来说变得非常重要,那么单线程异步解决方案可能会更好。.

每当任务被同一资源绑定时(例如,多个需要同时访问同一网络或本地资源),那么多线程可能会引入令人不满意的开销,而单线程异步 可能 引入更少的开销在这种资源有限的情况下,它也无法产生加速。在这种情况下,唯一的选择(如果你想要加速)是使该资源的多个副本可用(例如,如果稀缺资源是CPU,则为多个处理器核;如果稀缺资源,则支持更多并发连接的更好的数据库)是一个连接受限的数据库等)。.

另一种说法是:允许操作系统交换两个任务的单个资源的使用 不能 比仅让一个任务使用资源而另一个任务等待更快,然后让第二个任务完成连续。此外,交错的调度器成本意味着在任何实际情况下它实际上都会产生减速。是否发生CPU,网络资源,存储器资源,外围设备或任何其他系统资源的交错使用并不重要。.

3
ely

非阻塞I/O的一种可能实现正是您所说的,后台线程池阻塞I/O并通过一些回调机制通知I/O发起者的线程。实际上,这就是glibc中的 _ aio _ 模块的工作原理。 这里 是关于实施的一些模糊细节。.

虽然这是一个非常便携的好解决方案(只要你有线程),但操作系统通常能够更有效地为非阻塞I/O提供服务。 这篇维基百科文章 列出了线程池之外的可能实现。.

2
Miguel

我目前正在使用protothreads在嵌入式平台上实现async io。非阻塞io在16000fps和160fps之间的差异。非阻塞io的最大好处是,您可以构建代码来执行其他操作,而硬件可以执行其操作。甚至设备的初始化也可以并行完成。.

马丁

2
user2826084

让我举一个反例,异步I/O不起作用。我正在写一个类似于下面的代理 - 使用boost :: asio。https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp

但是,我的情况是,传入(来自客户端)消息很快,而传出(到服务器端)一个会话很慢,为了跟上传入速度或最大化总代理吞吐量,我们必须使用一个连接下的多个会话。.

因此,这个异步I/O框架不再起作用。我们需要一个线程池,通过为每个线程分配一个会话来发送到服务器。.

0
Zhidian Du

据我所知,改进是异步I/O使用(我说的是MS系统,只是为了澄清)所以 调用I/O完成端口 。通过使用异步调用,框架自动利用这种架构,这应该比标准线程机制更有效。作为个人经验,我可以说,如果您更喜欢AsyncCalls而不是阻止线程,那么您会明智地感觉您的应用程序更具反应性。.

0
Felice Pollano

在Node中,正在启动多个线程,但它在C++运行时是一个层。.

“所以是的NodeJS是单线程的,但这是一个半真半假,实际上它是事件驱动的,并且是后台工作者的单线程。主事件循环是单线程的,但大多数I/O工作在不同的线程上运行,因为Node.js中的I/O API在设计上是异步/非阻塞的,以便适应事件循环。“

https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea

“Node.js是非阻塞的,这意味着所有函数(回调)都被委托给事件循环,它们是(或可以)由不同的线程执行。这由Node.js运行时处理。”

https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98

“节点更快,因为它是非阻塞的......”解释是一些营销,这是一个很好的问题。它高效且可扩展,但不完全是单线程。.

0
SmokestackLightning