非阻塞IO与异步IO以及Java实现


73

尝试为自己总结这两个概念之间的区别(因为当我看到人们在一句话中同时使用这两个概念时,我感到非常困惑,例如“ Non-blocking async IO”,我试图弄清楚它是做什么的)意思)。

因此,以我的理解,无阻塞IO是操作系统的主要机制,如果有任何可用数据,则该OS处理IO,否则仅返回错误/不执行任何操作。

在异步IO中,您仅提供回调,当数据可用时,系统将通知您的应用程序。

那么,实际上什么是“非阻塞异步IO”?以及如何在Java中实现它们(标准JDK,没有外部库,我知道有java.nio.channels.{Channels, Selector, SelectorKey}java.nio.channels.{AsynchronousSocketChannel}):非阻塞IO,异步IO和非阻塞异步IO(如果有的话)?


3
“非阻塞异步I / O”只是毫无意义的双向通话。我不明白为什么您认为需要外部库。它们最终都只是操作系统功能的包装。
user207421 2014年

1
您正确理解这些术语。如前所述,“非阻塞异步IO”将是多余的。如果基础I / O机制是非阻塞的,则不需要异步,反之亦然。也许以这种方式描述的人都意味着它是非阻塞的,因为它已经被异步了。(示例:android-async-http库是围绕同步套接字I / O的异步包装器。)
Kevin Krumwiede 2014年

@KevinKrumwiede您能否提供一个示例,其中async-io实际上正在阻塞(我只能想象回调和主进程共享同一线程,并且在回调或类似物中有一个wait / future.get()) 。
2014年

Answers:


57

那么,实际上什么是“非阻塞异步IO”?

要回答这个问题,您必须首先了解没有阻塞异步I / O这样的事情。异步的概念要求没有等待,没有阻塞,没有延迟。当您看到非阻塞异步I / O时,该非阻塞位仅用于进一步限定该术语中的异步形容词。如此有效地,非阻塞异步I / O可能有点冗余。

I / O主要有两种。同步异步同步会阻塞当前执行线程,直到处理完成为止,而异步不会阻塞当前执行线程,而是将控制权传递给OS内核进行进一步处理。然后,当提交的任务完成时,内核会通知异步线程


异步通道组

Java中的异步通道的概念由异步通道组支持。异步通道组基本上会汇集多个通道以供重用。异步api的使用者从组中检索一个通道(JVM默认情况下会创建一个通道),并且该通道在完成其读/写操作后会自动将其自身放回到该组中。最终,异步通道组得到了惊喜,线程池的支持。同样,异步通道也是线程安全的。

支持异步通道组的线程池的大小由以下JVM属性配置

java.nio.channels.DefaultThreadPool.initialSize

给定一个整数值,它将设置该大小的线程池,以支持通道组。渠道组的创建和维护对开发者是透明的。


以及如何用Java实现它们

好吧,很高兴你问。这是一个示例AsynchronousSocketChannel(用于向Socket侦听服务器打开非阻塞客户端。)此示例摘录自Apress Pro Java NIO.2,并由我评论:

//Create an Asynchronous channel. No connection has actually been established yet
AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open(); 

/**Connect to an actual server on the given port and address. 
   The operation returns a type of Future, the basis of the all 
   asynchronous operations in java. In this case, a Void is 
   returned because nothing is returned after a successful socket connection
  */
Void connect = asynchronousSocketChannel.connect(new InetSocketAddress("127.0.0.1", 5000)).get();


//Allocate data structures to use to communicate over the wire
ByteBuffer helloBuffer = ByteBuffer.wrap("Hello !".getBytes()); 

//Send the message

Future<Integer> successfullyWritten=  asynchronousSocketChannel.write(helloBuffer);

//Do some stuff here. The point here is that asynchronousSocketChannel.write() 
//returns almost immediately, not waiting to actually finish writing 
//the hello to the channel before returning control to the currently executing thread

doSomethingElse();

//now you can come back and check if it was all written (or not)

System.out.println("Bytes written "+successfullyWritten.get());

编辑:我应该提到对异步NIO的支持来自JDK 1.7


8
共有三种:阻塞,非阻塞和异步。您已经错过了有关如何使用外部库在Java中实现它们的问题。
user207421 2014年

@EJP-Java中有内部对异步I / O的支持,而没有外部库,我的回答就是这样。关于阻塞/非阻塞问题,是否存在阻塞异步I / O?如果您有样本,我很乐意更新我的答案
kolossus 2014年

7
异步I / O通常是异步的,因为I / O机制正在阻塞。在这种情况下,异步只是意味着它是在另一个线程中完成的。
凯文·克鲁姆维德

因此,@ KevinKrumwiede根据您的定义是什么意思,每个I / O都在阻塞,问题是我们在什么时间/线程阻塞,对吗?比我们应该只谈论sync / async io,不要提及阻塞/非阻塞,因为它一直阻塞(可能不是立即阻塞,例如future.get()-等待结果),或者是从异步线程阻塞(我们在某些时候阻止异步线程执行)。
Aliaksandr Kazlou 2014年

我想所有I / O都会在某种程度上阻塞,如果不是在软件中,则在硬件中。无论你怎么称呼它阻挡取决于所显示的内容API给你,即是块是否你的线程。如果I / O在API外部是非阻塞的,那是因为在API的某个级别上它已被异步化。这就是为什么说“非阻塞异步I / O”是多余的。非阻塞和异步相互暗示。
凯文·克鲁姆维德2014年

114

我看到这是一个古老的问题,但是我认为这里遗漏了一些东西,@ nickdu试图指出这一点,但并不清楚。

与该讨论相关的IO有四种类型:

阻止IO

非阻塞IO

异步IO

异步无阻塞IO

我认为由于定义不明确而引起混淆。因此,让我尝试澄清一下。

首先让我们谈谈IO。当我们的IO速度很慢时,这很明显,但是IO操作可以是阻塞的也可以是非阻塞的。这与线程无关,与操作系统的接口无关。当我要求操作系统进行IO操作时,我可以选择等待所有数据准备就绪(阻塞),或者获取当前可用的数据并继续运行(非阻塞))。默认为阻塞IO。使用阻塞IO编写代码要容易得多,因为路径更清晰。但是,您的代码必须停止并等待IO完成。非阻塞IO需要使用选择和读取/写入而不是提供方便操作的更高级别的库与较低级别的IO库进行接口。非阻塞IO还意味着在OS进行IO时,您需要处理一些事情。这可能是多个IO操作或已完成的IO上的计算。

阻塞IO-应用程序在继续之前等待操作系统收集所有字节以完成操作或到达末尾。这是默认值。为了使技术更加清楚,启动IO的系统调用将安装一个信号处理程序,等待IO操作进行时发生的处理器中断。然后,系统调用将开始睡眠,该睡眠将当前进程的操作暂停一段时间,或者直到发生进程中断为止。

非阻塞IO-应用程序告诉OS,它只希望立即获得可用的字节,并在OS同时收集更多字节的同时继续前进。该代码使用select确定哪些IO操作具有可用字节。在这种情况下,系统调用将再次安装信号处理程序,而不是睡眠,它将信号处理程序与文件句柄相关联,并立即返回。该过程将负责定期检查文件句柄是否设置了中断标志。通常通过选择调用来完成。

现在,异步是混乱开始的地方。异步的一般概念仅表示在执行后台操作的同时该过程仍在继续,发生这种情况的机制并不具体。该术语含糊不清,因为非阻塞IO和线程阻塞IO都可以被认为是异步的。两者都允许并发操作,但是资源要求不同,代码也实质上不同。因为您已经问了一个问题“什么是非阻塞异步IO”,所以我将对执行IO的异步线程系统使用更严格的定义,该系统可能会也可能不会阻塞。

一般定义

异步IO-编程IO,允许进行多个并发IO操作。IO操作是同时发生的,因此代码不会等待未准备好的数据。

更严格的定义

异步IO-使用线程或多处理以允许并发IO操作发生的程序IO。

现在,有了这些更清晰的定义,我们有了以下四种IO范例类型。

阻塞IO-标准单线程IO,应用程序在该标准中等待所有IO操作完成后再继续。易于编码,没有并发性,对于需要多个IO操作的应用程序来说速度很慢。进程或线程将在等待IO中断发生时进入睡眠状态。

异步IO-线程IO,其中应用程序使用执行线程来同时执行Blocking IO操作。需要线程安全的代码,但通常比替代方法更容易读写。获得多个线程的开销,但执行路径清晰。可能需要使用同步的方法和容器。

非阻塞IO-应用程序在其中使用select来确定哪些IO操作已准备好前进的单线程IO,从而在OS处理并发IO时允许执行其他代码或其他IO操作。该进程在等待IO中断时不会休眠,而是负责检查文件句柄上的IO标志。由于不需要使用select检查IO标志,因此代码复杂得多,尽管不需要线程安全代码或同步的方法和容器。执行开销低,但代价是代码复杂性。执行路径是复杂的。

异步非阻塞IO-IO的一种混合方法,旨在通过使用线程来降低复杂性,同时在可能的情况下通过使用非阻塞IO操作来保持可伸缩性。这将是最复杂的IO类型,需要同步的方法和容器以及复杂的执行路径。这不是应该轻易进行编码的IO类型,通常仅在使用会掩盖复杂性的库(例如Future和Promises)时使用。


6
诸如AKKA和vert.x之类的框架支持非阻塞功能。人们经常将它们混淆为非阻塞IO框架。这些框架可以做很多事情,但不能做非阻塞IO。它们仅支持如上所述的异步IO。
Shibashis

5
这是最准确的答案
kimathie

1
谢谢。这可能是一个非常令人困惑的主题,尤其是根据项目和平台,许多术语可以互换使用并且有所不同。显然,这是OP的混乱,我希望为他和其他人消除混乱。
亚伦

3
仍然没有关注您,我解释了您要提出的观点。与异步IO是单线程或多线程有关。为了使IO在单线程执行中异步,您必须使用非阻塞IO。为了使IO在线程执行中被宽松地认为是异步的,可以使用阻塞的线程,也可以将非阻塞的IO与未阻塞的线程一起使用。因此,非阻塞io(单线程异步)是非常复杂的非阻塞io,具有称为非阻塞异步IO的线程。那您怎么称呼中间的那个是阻塞线程的异步IO?
亚伦

1
我选择并明确说明了为什么将其区分为“异步IO”。它仅仅是代数的。A = B + C其中A =“非阻塞式IO”,B =“非阻塞式IO”,因此对于C求解,我们有“异步IO”。
亚伦

5

非阻塞IO是指执行IO的调用立即返回并且不阻塞线程的情况。

知道IO是否完成的唯一方法是轮询其状态或阻止。认为它是一个Future。您开始IO操作,并返回Future。您可以调用isDone()它来检查是否已完成,如果已完成,请执行所需操作,否则,请继续进行其他操作,直到下次您要检查其是否完成为止。或者,如果您无事可做,则可以调用get它,它将阻塞直到完成。

异步IO是当执行IO的调用通知您它是通过事件而不是通过其返回值完成的。

这可以是阻塞的也可以是非阻塞的。

阻止异步IO

阻塞异步IO的意思是执行IO的调用是普通的阻塞调用,但是您所调用的东西将该调用包装在线程内,该线程将阻塞直到IO完成,然后委托对IO结果的处理到您的回调。也就是说,仍然有一个线程在堆栈的下方,该线程在IO上被阻塞,但您的线程不是。

非阻塞异步IO

这实际上是最常见的一种,它意味着不需要像标准非阻塞IO一样轮询非阻塞IO的状态,而是在完成后会调用您的回调。与阻塞异步IO相对,该线程在堆栈的任何位置都没有阻塞线程,因此它更快并且使用更少的资源,因为在不阻塞线程的情况下管理了异步行为。

您可以将其视为CompletableFuture。它要求您的程序具有某种形式的异步事件框架,该框架可以是多线程的,也可以是多线程的。因此,有可能在另一个线程中执行回调,或者安排在当前任务完成后在现有线程上执行回调。

我在这里更全面地解释了区别


1
回调既不是阻塞也不是非阻塞。我从未见过一个框架/语言/系统,在该框架/语言/系统中,线程将停止等待回调的调用,然后在启动回调的地方再次开始。也许确实存在这样的系统,但这将是非常奇怪的。如您所说,回调通常是注册的,并且执行独立于回调而继续。当问题是不可知论或以Java为中心时,此答案感觉非常以JavaScript为中心。
亚伦

1
看看我对底层IO如何发生的说明。我认为这对您有所帮助。
亚伦

1
@AaronM我编辑了答案,以摆脱我认为给您的印象,使我感到困惑。您的回答是好的,但是我觉得技术细节太详细了。我也不同意您的某些语义,但只是略微不同意。我的示例基于Java,我的答案中没有JavaScript。我觉得它普遍适用于所有语言和操作系统。您是否仍然看到任何令人困惑的地方,或者您现在不同意?
Didier A.

1
有道理,我现在更喜欢它。我唯一的问题是异步非阻塞。从开发人员层来看,它似乎是准确的,但是从系统层来看,它不是正确的。如果IO处于非阻塞状态,则必须进行检查以查看IO是否完成。内核不会自动调用堆栈中的函数。但是正如您提到的,这需要一个框架,而该框架将为开发人员管理这种复杂性。谢天谢地。
亚伦

1
至于JavaScript注释,我应该说的是,它倾向于事件/功能编程环境,我仍然认为是。这在Java中并不常见,而在JavaScript中却很常见,因此我做了评论。但是,所有这些类型的IO也都用于非事件驱动的代码,传统的过程代码中也是如此。在这种情况下,异步会变得更加复杂,但是非常有可能在不使用回调(或promise或future)的情况下进行非阻塞异步io。回调和其他替代方法确实使代码更易于遵循。
亚伦

4

我要说的是io的三种类型:

同步阻塞
同步非阻塞
异步

同步非阻塞和异步都将被视为非阻塞,因为调用线程没有等待IO完成。因此,尽管非阻塞异步io可能是多余的,但它们并不是同一个人。当我打开文件时,我可以以非阻塞模式打开它。这是什么意思?这意味着当我发出read()时,它将不会阻塞。它将向我返回可用的字节,或者指示没有可用的字节。如果我没有启用非阻塞io,则read()会阻塞,直到有可用数据为止。如果我希望一个线程处理多个io请求,则可能要启用非阻塞io。例如,我可以使用select()找出哪些文件描述符或套接字可以读取数据。然后,我对那些文件描述符进行同步读取。

异步io是您发出io请求的地方。该请求已排队,因此不会阻塞发出线程。请求失败或成功完成时,系统会通知您。


2

同步与异步

异步是一个相对术语,适用于所有类型的计算,而不仅仅是IO。有事不能是异步的本身,而是始终其他的东西。通常,异步性是指某些操作在相对于请求IO计算的线程的不同执行线程中发生,并且在请求线程和计算线程之间没有明确的同步(等待)。如果在计算线程正在工作时请求线程等待(休眠,阻塞),则我们将此类操作称为同步操作。也有混合情况。有时,发出请求的线程不会立即等待,而是在发出IO请求后异步执行一些固定数量的有用工作,但是如果它们尚不可用,则稍后阻塞(同步)以等待IO结果。

阻塞与非阻塞

从广义上讲,“阻塞”和“非阻塞”可以粗略地用来分别表示“同步”和“异步”。您将经常遇到“阻塞”与“同步”可互换使用,而“非阻塞”与“异步”可互换使用的情况。从这个意义上讲,“非阻塞异步”与上面提到的其他人一样是多余的。

但是,从更狭义的意义上讲,“阻塞”和“非阻塞”可能是指不同的内核IO接口。在这里值得一提的是,这些天所有的IO操作都是由OS内核执行的,因为对OS诸如磁盘或网络接口卡之类的IO硬件设备的访问是抽象的。这意味着您从用户空间代码请求的每个IO操作最终将由内核通过阻塞或非阻塞接口执行。

通过阻塞接口调用时,内核将假定您的线程想要同步获取结果,并将其置于睡眠状态(调度,阻塞),直到IO结果可用为止。因此,当内核满足IO请求时,该线程将无法执行任何其他有用的工作。例如,Linux上的所有磁盘IO都处于阻塞状态。

非阻塞内核接口的工作方式不同。您告诉内核您想要哪些IO操作。内核不会阻塞(调度)线程并立即从IO调用返回。然后,您的线程可以继续进行并做一些有用的工作。内核线程将异步满足IO请求。然后,您的代码需要偶尔检查内核是否已经完成其工作,然后您可以使用结果。例如,Linux提供了epoll非阻塞IO的接口。也有较早的调用pollselect系统调用出于相同的目的。值得注意的是,非阻塞接口主要适用于网络。

请注意,某些高级IO API在后台使用阻塞内核IO的事实并不意味着您的线程在调用该API时必然会阻塞。这样的API可以实现一种机制来生成新的或使用其他现有线程来执行该阻塞IO。稍后它将通过某种方式(回调,事件或让线程轮询)通知您的调用线程它已完成IO请求。也就是说,非阻塞IO语义可以通过使用其他线程在阻塞OS内核接口之上的第三方库或运行时在用户空间中实现。

结论

要了解每个特定的运行时或库如何实现IO异步,您将不得不更深入地了解它是产生新线程还是依赖异步内核接口。

后记

实际上,这些天您几乎没有机会遇到真正的单线程系统。

作为示例,大多数人将Node.js称为具有“单线程非阻塞” IO。但是,这是一种简化。在Linux上,真正的非阻塞IO仅可用于通过epoll接口进行网络操作。对于磁盘IO,内核将始终阻止调用线程。为了实现磁盘IO的异步性(这相对较慢),Node.js运行时(或libuv更精确地说)维护一个专用的线程池。每当请求异步磁盘IO操作时,运行时就会将工作分配给该池中的线程之一。该线程将执行标准的阻塞磁盘IO,而主(调用)线程将异步进行。更不用说众多线程,这些线程由V8运行时分别维护以进行垃圾回收和其他托管运行时任务。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.