我看到这是一个老问题,但我认为这里遗漏了一些东西,@nickdu 试图指出但不太清楚。
有四种与本次讨论相关的 IO:
阻塞 IO
非阻塞 IO
异步 IO
异步非阻塞 IO
我认为这种混淆是因为定义不明确。所以让我试着澄清一下。
首先让我们谈谈IO。当我们的 IO 很慢时,这是最明显的,但 IO 操作可以是阻塞的,也可以是非阻塞的。这与线程无关,它与操作系统的接口有关。当我向操作系统请求 IO 操作时,我可以选择等待所有数据准备好(阻塞),或者获取现在可用的数据并继续前进(非阻塞)。默认为阻塞 IO。使用阻塞 IO 编写代码要容易得多,因为路径更加清晰。但是,您的代码必须停止并等待 IO 完成。非阻塞 IO 需要与较低级别的 IO 库接口,使用 select 和 read/write 而不是提供方便操作的较高级别库。非阻塞 IO 还意味着在操作系统处理 IO 时您需要处理一些事情。这可能是已完成的 IO 上的多个 IO 操作或计算。
阻塞 IO - 应用程序等待操作系统收集所有字节以完成操作或到达末尾,然后再继续。这是默认设置。对于非常技术性的更清楚的是,启动 IO 的系统调用将安装一个信号处理程序,等待 IO 操作进行时将发生的处理器中断。然后系统调用将开始休眠,将当前进程的操作挂起一段时间,或者直到进程中断发生。
非阻塞 IO - 应用程序告诉操作系统它只需要现在可用的字节,并在操作系统同时收集更多字节时继续前进。该代码使用 select 来确定哪些 IO 操作具有可用字节。在这种情况下,系统调用将再次安装信号处理程序,但不是休眠,而是将信号处理程序与文件句柄相关联,并立即返回。该进程将负责定期检查文件句柄是否已设置中断标志。这通常通过 select 调用来完成。
现在异步是混乱的开始。异步的一般概念仅意味着在执行后台操作时进程继续,发生这种情况的机制并不具体。这个术语是模棱两可的,因为非阻塞 IO 和线程阻塞 IO 都可以被认为是异步的。两者都允许并发操作,但是资源需求不同,代码也有很大不同。因为您问了一个问题“什么是非阻塞异步 IO”,所以我将对异步使用更严格的定义,即执行 IO 的线程系统,它可能是非阻塞的,也可能不是非阻塞的。
一般定义
异步 IO - 允许发生多个并发 IO 操作的程序化 IO。 IO 操作同时发生,因此代码不会等待未准备好的数据。
更严格的定义
异步 IO - 使用线程或多处理来允许发生并发 IO 操作的程序化 IO。
现在有了这些更清晰的定义,我们就有了以下 四种 类型的 IO 范例。
阻塞 IO - 标准单线程 IO,其中应用程序在继续之前等待所有 IO 操作完成。对于需要多个 IO 操作的应用程序而言,易于编码、没有并发性并且速度很慢。进程或线程会在等待 IO 中断发生时休眠。
异步 IO - 应用程序使用执行线程同时执行阻塞 IO 操作的线程 IO。需要线程安全代码,但通常比替代方案更容易读写。获得多线程的开销,但执行路径清晰。可能需要使用同步方法和容器。
非阻塞 IO - 单线程 IO,应用程序使用 select 来确定哪些 IO 操作已准备好推进,允许在操作系统处理并发 IO 时执行其他代码或其他 IO 操作.进程在等待 IO 中断时不会休眠,而是负责检查文件句柄上的 IO 标志。由于需要使用 select 检查 IO 标志,因此代码复杂得多,但不需要线程安全代码或同步方法和容器。以代码复杂性为代价的低执行开销。执行路径错综复杂。
异步非阻塞 IO - 一种混合 IO 方法,旨在通过使用线程来降低复杂性,同时尽可能使用非阻塞 IO 操作来保持可扩展性。这将是最复杂的 IO 类型,需要同步方法和容器,以及复杂的执行路径。这不是应该考虑轻松编码的 IO 类型,并且通常仅在使用会掩盖复杂性的库时使用,例如 Futures 和 Promises。