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

yan*_*kee 111 io multithreading nonblocking blocking

我在网上搜索了一些关于阻止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中获得的回调.

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

Wer*_*nze 43

非阻塞或异步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(WriteFileOVERLAPPED结构)的完成情况.只有在代码WaitForMultipleObjectsEx使用bAlertableset to 调用时才会调用回调函数true.

在网上阅读更多内容:


Flo*_*scu 28

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


ely*_*ely 6

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

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

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

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

想象一下您可能遇到的许多不寻常或愚蠢的极端情况是很有用的。

“异步”不一定是并发的,例如上面的例子:你在一台只有一个处理器核心的机器上“异步”执行两个受 CPU 限制的任务。

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

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

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

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

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

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

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


小智 5

使用 AIO 的主要原因是为了可扩展性。在几个线程的上下文中查看时,好处并不明显。但是当系统扩展到 1000 个线程时,AIO 将提供更好的性能。需要注意的是,AIO 库不应引入进一步的瓶颈。