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

时间:2011-12-17 16:45:52

标签: multithreading io blocking nonblocking

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

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

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

  1. 要加载文件的内容,应用程序会将此任务委托给基于事件的i / o框架,并传递回调函数和文件名
  2. 事件框架委托给操作系统,该操作系统编程硬盘的DMA控制器直接将文件写入内存
  3. 事件框架允许运行更多代码。
  4. 完成磁盘到内存的复制后,DMA控制器会产生中断。
  5. 操作系统的中断处理程序通知基于事件的i / o框架有关文件被完全加载到内存中的信息。它是如何做到的?使用信号??
  6. 当前在事件i / o框架内运行的代码完成。
  7. 基于事件的i / o框架检查其队列,并从步骤5中查看操作系统的消息,并执行它在步骤1中获得的回调。
  8. 这是怎么回事?如果没有,它是如何工作的?这意味着事件系统可以在不需要显式触摸堆栈的情况下工作(例如需要备份堆栈并在切换线程时将另一个线程的堆栈复制到内存中的真实调度程序)?这实际节省了多少时间?还有更多吗?

9 个答案:

答案 0 :(得分:40)

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

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

  1. 每个连接一个线程(可以阻止I / O,但也可以是非阻塞I / O)。
    每个线程都需要内存资源(也是内核内存!),这是一个缺点。每个额外的线程意味着更多的工作为调度程序。
  2. 所有连接的一个主题。
    这会从系统中获取负载,因为我们有更少的线程。但它也会阻止您使用机器的全部性能,因为您可能最终将一个处理器驱动到100%并让所有其他处理器闲置。
  3. 一些线程,每个线程处理一些连接。
    这会从系统中获取负载,因为线程较少。它可以使用所有可用的处理器。在Windows上,Thread Pool API支持此方法。
  4. 当然拥有更多线程本身不是问题。您可能已经认识到我选择了相当多的连接/线程。我怀疑如果我们只讨论十几个线程,你会发现三种可能的实现之间有任何区别(这也是Raymond Chen在MSDN博客文章Does Windows have a limit of 2000 threads per process?上建议的那样)。

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

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

    网上阅读更多内容:

答案 1 :(得分:27)

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与多线程here主题进行的更详细的研究。

答案 2 :(得分:4)

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

答案 3 :(得分:3)

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

第一种情况通常被称为" parallel"节目。第二种情况通常被称为"并发"或"异步"编程,虽然"并发"有时也用于指代仅允许操作系统交错执行多个任务的情况,而不管这种执行是否必须串行发生或者是否可以使用多个资源来实现并行执行。在后一种情况下,"并发"通常指的是在程序中编写执行的方式,而不是从任务执行的实际同时性的角度来看。

用默会假设很容易谈论所有这些。例如,有些人可以快速提出索赔,例如"异步I / O将比多线程I / O更快。"由于几个原因,这种说法是可疑的。首先,可能是某些给定的异步I / O框架通过多线程精确实现的情况,在这种情况下它们是同一个,并且说一个概念"是没有意义的。快于"另一个。

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

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

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

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

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

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

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

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

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

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

马丁

答案 6 :(得分:0)

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

答案 7 :(得分:0)

在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

“节点更快,因为它不受阻碍……”的解释有点行销,这是一个很大的问题。它是高效且可伸缩的,但并非完全是单线程的。

答案 8 :(得分:0)

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

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

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