我在网上搜索了一些关于阻止I / O和非阻塞I / O的技术细节,我发现有几个人说非阻塞I / O比阻塞I / O更快 . 例如this document .
如果我使用阻塞I / O,那么当然阻塞的线程当然不会做任何其他事情......因为它被阻止了 . 但是一旦一个线程开始被阻塞,操作系统就可以切换到另一个线程而不会切换回来,直到阻塞线程有事情要做 . 因此,只要系统上有另一个需要CPU并且没有被阻塞的线程,与基于事件的非阻塞方法相比,不应该有更多的CPU空闲时间,是吗?
除了减少CPU空闲的时间外,我还看到了另外一个选项,可以增加计算机在给定时间范围内可以执行的任务数量:减少切换线程所带来的开销 . 但是怎么做呢?并且开销是否足以显示可衡量的影响?以下是关于如何将其工作的想法:
-
要加载文件的内容,应用程序将此任务委托给基于事件的i / o框架,并传递回调函数和文件名
-
事件框架委托给操作系统,该操作系统对硬盘的DMA控制器进行编程,将文件直接写入内存
-
事件框架允许运行更多代码 .
-
完成磁盘到内存复制后,DMA控制器会产生中断 .
-
操作系统的中断处理程序通知基于事件的i / o框架有关正在完全加载到内存中的文件 . 它是如何做到的?使用信号?
-
当前在事件I / O框架内运行的代码完成 .
-
基于事件的i / o框架检查其队列,并从步骤5中查看操作系统的消息,并执行它在步骤1中获得的回调 .
它是如何工作的?如果没有,它是如何工作的?这意味着事件系统可以在不需要显式触摸堆栈的情况下工作(例如需要备份堆栈并在切换线程时将另一个线程的堆栈复制到内存中的真实调度程序)?这实际节省了多少时间?还有更多吗?
7 回答
非阻塞或异步I / O的最大优点是您的线程可以并行地继续它的工作 . 当然,您也可以使用额外的线程来实现此目的 . 正如您所说的最佳整体(系统)性能,我想最好使用异步I / O而不是多线程(因此减少线程切换) .
让我们看一下网络服务器程序的可能实现,该程序将处理并行连接的1000个客户端:
每个连接一个线程(可以阻塞I / O,但也可以是非阻塞I / O) .
每个线程都需要内存资源(也是内核内存!),这是一个缺点 . 每个额外的线程意味着调度程序的工作量也会增加 .
所有连接都有一个线程 .
这会从系统中获取负载,因为我们有更少的线程 . 但它也会阻止您使用机器的全部性能,因为您可能最终将一个处理器驱动到100%并让所有其他处理器闲置 .
每个线程处理一些连接的几个线程 .
这会从系统中获取负载,因为线程较少 . 它可以使用所有可用的处理器 . 在Windows上,Thread Pool API支持此方法 .
当然,拥有更多线程本身不是问题 . 您可能已经认识到我选择了相当多的连接/线程 . 如果我们只讨论十几个线程,我怀疑你会发现三种可能的实现之间有什么区别(这也是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
结构)的完成情况 . 只有在代码调用WaitForMultipleObjectsEx
且bAlertable
设置为true
时,才会调用回调函数 .在网上阅读更多内容:
MSDN上的
Multiple Threads in the User Interface,也很快处理了创建线程的成本
部分Threads and Thread Pools说"Although threads are relatively easy to create and use, the operating system allocates a significant amount of time and other resources to manage them."
CreateThread documentation on MSDN说"However, your application will have better performance if you create one thread per processor and build queues of requests for which the application maintains the context information." .
旧文章Why Too Many Threads Hurts Performance, and What to do About It
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这一主题进行的更详细的研究 .
使用AIO的主要原因是可扩展性 . 在几个线程的上下文中查看时,好处并不明显 . 但是当系统扩展到1000个线程时,AIO将提供更好的性能 . 需要注意的是,AIO库不应该引入进一步的瓶颈 .
假设由于任何形式的多计算而导致速度提高,您必须假定多个基于CPU的任务正在多个计算资源(通常是处理器核心)上同时执行,或者并非所有任务都依赖于并发使用相同的资源 - 也就是说,某些任务可能依赖于一个系统子组件(例如磁盘存储),而某些任务依赖于另一个(从外围设备接收通信),还有一些任务可能需要使用处理器核心 .
第一种情况通常被称为“并行”编程 . 第二种情况通常被称为“并发”或“异步”编程,尽管“并发”有时也用于指代仅允许操作系统交错执行多个任务的情况,无论这种执行是否必须采取串行放置或者如果可以使用多个资源来实现并行执行 . 在后一种情况下,“并发”通常是指在程序中编写执行的方式,而不是从任务执行的实际同时性的角度 .
用默认的假设很容易谈论所有这些 . 例如,有些人很快就会提出诸如“异步I / O将比多线程I / O更快”的主张 . 由于几个原因,这种说法是可疑的 . 首先,可能是某些给定的异步I / O框架精确地使用多线程实现的情况,在这种情况下它们是同一个,并且说一个概念“比另一个更快”没有意义 .
其次,即使在存在异步框架的单线程实现(例如单线程事件循环)的情况下,您仍然必须假设该循环正在做什么 . 例如,您可以使用单线程事件循环执行的一件愚蠢的事情是请求它异步完成两个不同的纯CPU绑定任务 . 如果你在只有一个理想化的单处理器内核(忽略现代硬件优化)的机器上执行此操作,那么“异步”执行此任务与执行它与两个独立管理的线程或仅使用一个单独的进程实际上没有任何不同 - - 差异可能归结为线程上下文切换或操作系统调度优化,但如果两个任务都进入CPU,则两种情况都类似 .
想象一下你可能遇到的很多不寻常或愚蠢的角落情况是很有用的 .
“异步”不必是并发的,例如,如上所述:您在具有一个处理器核心的机器上“异步”执行两个CPU绑定任务 .
多线程执行没有't have to be concurrent: you spawn two threads on a machine with a single processor core, or ask two threads to acquire any other kind of scarce resource (imagine, say, a network database that can only establish one connection at a time). The threads'执行可能是交错的,但操作系统调度程序认为合适,但它们的总运行时间不能减少(并且将从线程增加单个核心上的上下文切换(或者更一般地说,如果你生成的线程多于运行它们的核心,或者有更多的线程要求资源而不是资源可以支持的话题) . 同样的事情也适用于多处理 .
因此,异步I / O和多线程都不能在运行时方面提供任何性能提升 . 他们甚至可以放慢速度 .
但是,如果您定义一个特定的用例,就像一个特定的程序,它既可以进行网络调用,从远程数据库等网络连接资源中检索数据,也可以进行一些本地CPU绑定计算,那么您可以开始推理给出了关于硬件的特定假设,两种方法之间的性能差异 .
要问的问题:我需要执行多少计算步骤以及有多少独立的资源系统来执行它们?是否存在需要使用独立系统子组件的计算步骤的子集,并且可以同时从中受益?我有多少处理器核心以及使用多个处理器或线程在不同核心上完成任务的开销是多少?
如果您的任务主要依赖于独立的子系统,那么异步解决方案可能会很好 . 如果处理它所需的线程数量很大,那么上下文切换对于操作系统来说变得非常重要,那么单线程异步解决方案可能会更好 .
每当任务被同一资源绑定时(例如,多个需要同时访问同一网络或本地资源),那么多线程可能会带来令人不满意的开销,而单线程异步可能会在这样的资源中引入更少的开销 - 有限的情况它也不能产生加速 . 在这种情况下,唯一的选择(如果你想要加速)是使该资源的多个副本可用(例如,如果稀缺资源是CPU,则为多个处理器核;如果稀缺资源,则支持更多并发连接的更好的数据库是一个连接受限的数据库等) .
另一种说法是:允许操作系统交错使用单个资源用于两个任务不能比仅让一个任务使用资源而另一个任务等待更快,然后让第二个任务连续完成 . 此外,交错的调度器成本意味着在任何实际情况下它实际上都会产生减速 . 是否发生CPU,网络资源,存储器资源,外围设备或任何其他系统资源的交错使用并不重要 .
非阻塞I / O的一种可能实现正是您所说的,后台线程池阻塞I / O并通过一些回调机制通知I / O发起者的线程 . 实际上,这就是glibc中AIO模块的工作原理 . Here是关于实施的一些模糊细节 .
虽然这是一个非常便携的好解决方案(只要你有线程),但操作系统通常能够更有效地为非阻塞I / O提供服务 . This Wikipedia article列出了线程池之外的可能实现 .
我目前正在使用protothreads在嵌入式平台上实现async io . 非阻塞io在16000fps和160fps之间的差异 . 非阻塞io的最大好处是你可以构建你的代码来做其他事情,而硬件做它的事情 . 甚至设备的初始化也可以并行完成 .
马丁
据我所知,改进是异步I / O使用(我说的是MS系统,只是为了澄清)所以called I/O completion ports . 通过使用异步调用,框架自动利用这种架构,这应该比标准线程机制更有效 . 作为个人经验,我可以说,如果您更喜欢AsyncCalls而不是阻止线程,那么您会明智地感觉您的应用程序更具反应性 .