2011-12-17 139 views
88

我在网上搜索了关于阻塞I/O和非阻塞I/O的一些技术细节,并且我发现有几个人表示非阻塞I/O比阻塞I/O要快。例如在this document非阻塞I/O是否比多线程阻塞I/O更快?怎么样?

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

除了减少时的CPU为空闲I看到一个选择,以增加一台计算机可以在给定时间帧中执行的任务数:减少通过切换线程引入的开销。但是,这怎么做呢?是否有足够的开销来显示可衡量的影响?下面是我怎么能想象它的工作的想法:

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

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

+3

简短的回答:它更多的是每个连接有一个线程的开销。非阻塞的io可以避免每个连接都有一个线程。 – 2011-12-17 17:03:25

+7

在一个系统中,阻塞IO的成本很高,因为您不能创建与连接数量相同的线程。在JVM上,您可以创建几千个线程,但如果连接数超过100.000,该怎么办?所以你必须坚持一个异步解决方案。但是,有些线程并不昂贵的语言(例如绿色线程),就像Go/Erlang/Rust那样,如果线程数不超过100.000,就不会有问题。当线程数量可能很大时,我认为阻塞IO可以缩短响应时间。但那也是我不得不问专家是否在现实中成立的原因。 – OlliP 2014-03-08 09:55:42

+0

@OliverPlow,我也这么认为,因为阻止IO通常意味着我们让* system *处理“并行管理”,而不是使用任务队列等自己完成它。 – Pacerier 2014-06-18 19:22:11

回答

32

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

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

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

当然有更多的线程本身不是一个问题。正如你可能已经认识到我选择了相当多的连接/线程。如果我们只谈论十几个线程(这也是Raymond Chen在MSDN博客文章Does Windows have a limit of 2000 threads per process?中建议的),我怀疑你会看到三种可能的实现之间的区别。

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

您描述的步骤1到7给出了它是如何工作的好主意。在Windows上,操作系统会通过事件或回调通知您完成异步I/O(WriteFileOVERLAPPED结构)。回拨功能只会在您的代码呼叫WaitForMultipleObjectsExbAlertable设置为true时才会被调用。

在网络上的一些更多阅读:

+0

从网络的角度来看,常见的知识(互联网,专家的意见)建议大大提高最大值。由于内存增加和上下文切换时间,请求线程的数量在阻止IO(使请求处理更慢)方面是一件坏事,但是,在将工作推迟到另一个线程时,Async IO是否做同样的事情?是的,你现在可以提供更多的请求,但是在后台有相同数量的线程..那真正的好处是什么? – JavierJ 2015-11-25 22:19:01

+1

@JavierJ你似乎相信,如果n个线程做异步文件IO,另外n个线程将被创建来执行阻塞文件IO?这不是真的。操作系统具有异步文件IO支持,并且在等待IO完成时不需要阻塞。它可以对IO请求进行排队,并且如果发生硬件(例如DMA)中断,它可以将请求标记为已完成并设置一个事件来通知调用者线程。即使需要额外的线程,操作系统也能够使用该线程处理来自多个线程的多个IO请求。 – 2015-11-26 09:00:12

+0

谢谢,它涉及操作系统异步文件IO支持是有意义的,但是当我编写代码实际执行此操作时(从Web的角度来看)与Java Servlet 3.0 NIO说我仍然看到一个线程的请求和后台线程(异步)循环读取文件,数据库或其他。 – JavierJ 2015-11-30 18:22:44

0

据我所知道的改进是异步I/O的使用(我说的是MS系统,只是为了澄清)所谓called I/O completion ports。通过使用异步调用,框架可以自动利用这种架构,并且这应该比标准线程机制更高效。作为一种个人体验,我可以说,如果你更喜欢AsyncCalls而不是阻塞线程,你会明显地感觉到你的应用程序更具反应性。

2

一种可能的实现非阻塞I/O的是你说什么,用的那些阻塞I/O,并通过一些回调机制通知I/O的鼻祖线程后台线程池。实际上,这就是glibc中的AIO模块的工作原理。 Here是关于实现的一些模糊细节。

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

4

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

20

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主题所做的更详细的研究。

+0

我想知道是否值得区分预计完成的I/O操作和不可能[例如“获取到达串口的下一个字符”,在远程设备可能或不可以发送任何东西的情况下]。如果I/O操作预计在合理时间内完成,则可能会延迟清理相关资源,直到操作完成。但是,如果操作可能永远不会完成,那么这种延迟将是不合理的。 – supercat 2013-05-13 19:34:26

+0

@supercat您正在描述的场景用于较低级别的应用程序和库中。服务器依赖于它,因为它们不断等待传入连接。如上所述的异步I/O不适合这种情况,因为它基于启动特定操作并注册回调完成。在你描述的情况下,你需要在系统事件上注册回调,并处理每个通知。您不断处理输入而不是执行操作。如上所述,这通常在低层次上完成,几乎从未在您的应用程序中完成。 – 2013-05-14 08:38:48

+0

该模式对于各种类型硬件附带的应用程序很常见。串行端口并不像以往那样普遍,但模拟串行端口的USB芯片在专用硬件设计中非常流行。由于操作系统无法知道输入字符序列的意思,所以在应用程序级别处理来自这些事物的字符。一个钱箱被打开,通知应该发送到某个地方。 – supercat 2013-05-14 14:56:21

2

我目前正在使用protothreads在嵌入式平台上实现异步io。非阻塞io使运行在16000fps和160fps之间的差异。非阻塞io的最大好处是,您可以将代码构造为在硬件完成时做其他事情。甚至可以并行完成设备的初始化。

马丁

2

要设定一个速度的提高所导致的任何形式的多计算,你必须假定,要么多基于CPU的任务正在于多个计算资源(通常是处理器核心)并行执行,否则,不是所有的的任务依赖于同一资源的并发使用 - 也就是说,一些任务可能依赖于一个系统子组件(比如磁盘存储),而一些任务依赖于另一个(接收来自外围设备的通信),还有一些任务可能需要处理器内核的使用。

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

用默认的假设来谈论所有这一切很容易。例如,有些人可以很快做出诸如“异步I/O比多线程I/O更快”的说法。这个说法有几个原因是可疑的。首先,可能会出现这样的情况:某些给定的异步I/O框架可以精确地使用多线程来实现,在这种情况下它们是相同的,并且说一个概念“比另一个更快”是没有意义的。第二,即使在有异步框架(如单线程事件循环)的单线程实现的情况下,您仍然必须假定该循环正在做什么。例如,您可以使用单线程事件循环做的一件愚蠢的事情是请求它异步完成两个不同的纯粹CPU限制的任务。如果你只在一台只有理想化单处理器内核的机器上进行(忽略现代硬件优化),那么“异步”执行这个任务不会有任何不同,而不是用两个独立管理的线程执行任务,或者只用一个独立的进程执行 - - 这种差异可能归结为线程切换或操作系统进度优化,但是如果两种任务都转到CPU,那么在任何情况下都是相似的。

想象你会遇到很多不寻常或愚蠢的角落案例是很有用的。

“异步”并非必须是并发的,例如,如上所述:“异步”在具有一个处理器内核的计算机上执行两个CPU限制任务。多线程执行并不一定是并发的:在一个处理器核心的机器上产生两个线程,或者要求两个线程获取任何其他类型的稀缺资源(想象一下,一个网络数据库一次只能建立一个连接)。线程的执行可能是交错然而,操作系统调度程序看起来合适,但它们的总运行时间不能在单个内核上减少(并且将从线程上下文切换中增加)(或者更一般地,如果产生的线程多于有核心来运行它们,或者有更多的线程要求资源而不是资源能够维持的资源)。同样的事情也适用于多处理。

因此,就运行时而言,异步I/O和多线程都不能提供任何性能增益。他们甚至可以放慢速度。

但是,如果您定义了一个特定的用例,就像一个特定的程序一样,这个程序既可以通过网络调用来从网络连接的资源(如远程数据库)检索数据,也可以执行一些本地CPU计算,在给出关于硬件的特定假设的情况下开始推断两种方法之间的性能差异。

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

如果您的任务很大程度上依赖于独立的子系统,那么异步解决方案可能会很好。如果处理它所需的线程数量很大,以至于操作系统的上下文切换变得不平凡,那么单线程异步解决方案可能会更好。当任务被同一资源绑定(例如多个需要同时访问同一个网络或本地资源)时,那么多线程可能会引入不令人满意的开销,并且尽管单线程异步可能会导致较少的开销在这种资源有限的情况下,它也不能产生加速。在这种情况下,唯一的选择(如果你想加快速度)就是使该资源的多个副本可用(例如,如果稀缺资源是CPU,则为多个处理器内核;如果稀缺资源是支持更多并发连接的更好的数据库是连接受限的数据库等)。

另一种方式把它是:允许操作系统交错单个资源的使用情况两个任务不能是不是仅仅让一个任务中使用的资源,而其他等待,然后让第二个任务完成快连续。此外,交织的调度器成本意味着在任何实际情况下,它实际上会造成放缓。 CPU,网络资源,内存资源,外围设备或任何其他系统资源是否发生交叉使用并不重要。