异步和非阻塞调用?也是在阻塞和同步之间

异步调用和非阻塞调用之间的区别是什么?在阻塞和同步调用之间(请提供示例)?

158964 次浏览

它们只在拼写上有所不同。它们所指的内容没有区别。从技术上讲,你可以说它们的重点不同。非阻塞指的是控制流(它不阻塞)。异步是指当事件\数据被处理时(不是同步的)。

在许多情况下,它们是同一事物的不同名称,但在某些情况下,它们是完全不同的。这要看情况。在整个软件行业中,术语的应用并不是完全一致的。

例如,在经典的套接字API中,非阻塞套接字是一个简单地立即返回一个特殊的“将阻塞”的套接字。错误消息,而阻塞套接字将被阻塞。你必须使用一个单独的函数,如selectpoll,以找出何时是重试的好时机。

但是异步套接字(Windows套接字支持)或。net中使用的异步IO模式更方便。您调用一个方法来启动一个操作,当操作完成时,框架会回调您。即使在这里,也有基本的区别。异步Win32套接字"它们的结果通过传递Window消息到一个特定的GUI线程上,而. net异步IO是自由线程的(你不知道你的回调将被调用在哪个线程上)。

所以他们并不总是指同一件事。要提取套接字示例,我们可以这样说:

  • 阻塞和同步意味着同样的事情:你调用API,它挂起线程,直到它得到某种答案并返回给你。
  • 非阻塞意味着如果一个答案不能快速返回,API 立即返回会报错,并且不做任何其他事情。所以有必须有一些相关的方法来查询API是否准备好被调用(即以有效的方式模拟等待,以避免在紧循环中手动轮询)。
  • 异步意味着API 总是立即返回,已经启动了一个“background"努力来满足你的请求,所以一定有一些相关的方法来获得结果。
  • 异步指的是并行所做的事情,比如另一个线程。
  • 非阻塞通常指轮询,即检查给定条件是否成立(套接字是可读的,设备有更多数据,等等)。

非阻塞:该函数在栈上时不会等待。

异步:在函数调用离开堆栈后,该函数调用的工作可以继续进行

正如你可能从众多不同的(通常是相互排斥的)答案中看到的,这取决于你问谁。在某些领域,这两个术语是同义词。或者它们可能分别指两个相似的概念:

  • 一种解释是,调用将在后台做一些基本上不受监督的事情,以允许程序不被它不需要控制的冗长进程所阻塞。播放音频可能是一个例子——一个程序可以调用一个函数来播放(比如说)一个mp3,从那时起可以继续做其他事情,而把它留给操作系统来管理在声音硬件上渲染音频的过程。
  • 另一种解释是,调用将做一些程序需要监视的事情,但将允许大部分流程在后台发生,只在流程的关键点通知程序。例如,异步文件IO可能是一个例子——程序向操作系统提供一个缓冲区来写入文件,而操作系统只在操作完成或发生错误时通知程序。

在任何一种情况下,目的是允许程序不被阻塞,等待一个缓慢的进程完成-程序如何响应是唯一真正的区别。不同的程序员、不同的语言、不同的平台都不一样。或者这些术语可能指的是完全不同的概念(例如在线程编程中使用同步/异步)。

抱歉,但我不相信有一个唯一的正确答案是全面正确的。

屏蔽:控件在原语(sync或async)处理完成后返回调用进程

非阻塞:控件在调用后立即返回进程

把这个问题放在java 7中的NIO和NIO.2上下文中,异步IO比非阻塞高级了一步。 使用java NIO非阻塞调用,可以通过调用AbstractSelectableChannel.configureBlocking(false)来设置所有通道(SocketChannel, ServerSocketChannel, FileChannel等)。 然而,在这些IO调用返回之后,您可能仍然需要控制检查,例如是否以及何时再次读/写,等等 例如,

while (!isDataEnough()) {
socketchannel.read(inputBuffer);
// do something else and then read again
}
使用java 7中的异步api,可以以更通用的方式制作这些控件。 这两种方法之一是使用CompletionHandler。注意两个read调用都是非阻塞的
asyncsocket.read(inputBuffer, 60, TimeUnit.SECONDS /* 60 secs for timeout */,
new CompletionHandler<Integer, Object>() {
public void completed(Integer result, Object attachment) {...}
public void failed(Throwable e, Object attachment) {...}
}
}

阻塞调用:控件仅在调用完成时返回。

非阻塞调用:立即返回控件。之后的操作系统以某种方式通知进程调用已经完成。


同步程序:使用阻塞调用的程序。为了在调用期间不被冻结,它必须有2个或更多的线程(这就是为什么它被称为同步-线程同步运行)。

异步程序:使用非阻塞调用的程序。它可以只有一个线程,但仍然保持交互。

非阻塞调用立即返回任何可用的数据:请求的全部字节数,更少,或根本没有。

异步调用请求的转移将在其整体中执行,但将在未来的某个时间完成。

同步/异步是描述两个模块之间的关系 阻塞/非阻塞是描述一个模块的情况

< p >一个例子:< br > 模块十:"I".
模块Y: "bookstore".
X问Y:你有《c++入门》这本书吗?< br > < / p >
  1. 阻塞:在Y回答X之前,X一直在那里等待答案。现在X(一个模块)阻塞了。X和Y是两个线程或两个进程还是一个线程或一个进程?我们不知道。

  2. 非阻塞:在Y回答X之前,X只是离开那里去做其他事情。我们只知道X在Y完成它的工作之前可以做其他事情。这里X(一个模块)是非阻塞的。X和Y是两个线程还是两个进程还是一个进程?我们不知道。但是我们确定X和Y不可能是一个线程

  3. 同步:在Y回答X之前,X一直在那里等待答案。这意味着X不能继续,直到Y完成它的工作。现在我们说:X和Y(两个模块)是同步的。X和Y是两个线程或两个进程还是一个线程或一个进程?我们不知道。

  4. 异步:在Y回答X之前,X离开那里,X可以做其他工作。现在我们说:X和Y(两个模块)是异步的。X和Y是两个线程还是两个进程还是一个进程?我们不知道。但是我们确定X和Y不是一个线程


请注意上面两个加粗的句子。为什么题目中的粗体字包含两种情况,而题目中的粗体字只包含一种情况?这是区分非阻塞和异步的关键。

让我试着用另一种方式来解释这四个词:

  1. OMG,我被冻住了!我动不了!我必须等待特定的事件发生。如果那样的话,我就得救了!

  2. 非阻塞:我被告知我必须等待特定事件发生。好的,我理解,我保证我会等的。但是在等待的时候,我还可以做一些其他的事情,我没有被冻住,我还活着,我可以跳,我可以走,我可以唱歌等等。

  3. 我妈妈要做饭,她让我去买些肉。我刚对妈妈说:我们是同步的!我很抱歉,但你必须等,即使我可能需要100年才能拿回一些肉……

  4. 我们要做一个披萨,我们需要番茄和芝士。现在我说:我们去购物吧。我去买些西红柿,你去买些奶酪。我们不需要等待对方,因为我们是异步的。

这里有一个关于非阻塞的典型例子&同步:< br >

// thread X
while (true)
{
msg = recv(Y, NON_BLOCKING_FLAG);
if (msg is not empty)
{
break;
}
else
{
sleep(2000); // 2 sec
}
}


// thread Y
// prepare the book for X
send(X, book);
你可以看到这个设计是非阻塞的(你可以说这个循环大部分时间都在做一些无意义的事情,但在CPU的眼中,X正在运行,这意味着X是非阻塞的。如果你愿意,你可以用任何其他代码替换sleep(2000)),而X和Y (两个模块)是同步的,因为X不能继续做任何其他事情(X不能跳出循环),直到它从Y得到书。
通常在这种情况下,使X成为阻塞会更好,因为非阻塞会为一个愚蠢的循环花费很多资源。但是这个例子很好地帮助你理解一个事实:非阻塞并不意味着异步

这四个字确实很容易让我们困惑,我们应该记住的是,这四个字是为建筑设计服务的。学习如何设计一个好的架构是区分它们的唯一方法。

例如,我们可以设计这样一种架构:

// Module X = Module X1 + Module X2
// Module X1
while (true)
{
msg = recv(many_other_modules, NON_BLOCKING_FLAG);
if (msg is not null)
{
if (msg == "done")
{
break;
}
// create a thread to process msg
}
else
{
sleep(2000); // 2 sec
}
}
// Module X2
broadcast("I got the book from Y");




// Module Y
// prepare the book for X
send(X, book);

在这个例子中,我们可以说

  • X1是非阻塞的
  • X1和X2是同步的
  • X和Y是异步的

如果需要,还可以用这四个字来描述在X1中创建的线程。

再一次:这四个字是为建筑设计服务的。所以我们需要的是一个合适的架构,而不是像语言律师一样区分这四个词。如果你遇到一些情况,你不能很清楚地区分这四个词,你应该忘记这四个词,用你自己的话来描述你的架构。

所以更重要的事情是:我们什么时候使用同步而不是异步?什么时候用阻塞代替非阻塞?X1的阻塞性比非阻塞性更好吗?X和Y是同步的还是异步的?为什么Nginx是非阻塞的?Apache为什么阻塞?这些问题是你必须弄清楚的。

为了做出正确的选择,您必须分析您的需求并测试不同体系结构的性能。没有这样一种体系结构可以满足各种需求。

阻塞模型要求初始应用程序在I/O开始时阻塞。这意味着不可能同时重叠处理和I/O。同步非阻塞模型允许处理和I/O重叠,但它要求应用程序反复检查I/O的状态。这就留下了异步非阻塞I/O,允许处理和I/O重叠,包括I/O完成的通知。

同步被定义为同时发生(在可预测的时间,或在可预测的顺序)。

异步被定义为不同时发生。(不可预知的时间或顺序)。

这就是导致第一个混乱的原因,即异步是某种同步方案,是的,它是用来表示这个意思的,但实际上它描述的是在何时或以何种顺序运行方面不可预测地发生的进程。这样的事件通常需要同步,以使它们正确地运行,其中存在多个同步方案,其中一个称为阻塞,另一个称为非阻塞,还有一个令人困惑的称为异步

因此,您可以看到,整个问题是关于找到同步异步行为的方法,因为您有一些操作需要另一个操作的响应才能开始。因此这是一个协调问题,你怎么知道你现在可以开始操作了?

最简单的解决方案称为阻塞。

阻塞是当你只是选择等待另一件事完成,并返回你一个响应,然后再继续需要它的操作。

所以如果你需要在吐司上涂黄油,那么你首先需要烤的是有教养的。你协调它们的方式是,你先烤那些有繁殖能力的面包,然后不停地盯着烤面包机,直到它把面包炸开,然后你继续往它们上面涂黄油。

这是最简单的解决方案,而且效果很好。没有真正的理由不使用它,除非你碰巧也有其他事情需要做,不需要与操作协调。例如,洗碗。为什么要一直盯着烤面包机无所事事地等着烤面包片爆开呢?你知道这需要一点时间,而且你可以在烤完的时候洗一整个盘子。

这就是另外两种解决方案(分别称为非阻塞和异步)发挥作用的地方。

非阻塞是当你在等待操作完成时选择做其他不相关的事情。在您认为合适的时候检查响应的可用性。

所以与其盯着烤面包机等它炸开。你去洗一整个盘子。然后你偷看烤面包机,看看烤面包有没有裂开。如果还没有,你就去洗另一个盘子,每洗一个盘子之间都要检查一下烤面包机。当你看到烤面包已经爆了,你就不再洗碗,而是拿起烤面包,继续往上面抹黄油。

不过,不断地检查烤面包片是很烦人的,想象一下烤面包机在另一个房间。在吃菜的间隙,你会浪费时间去另一个房间检查吐司。

这里出现了异步。

异步是当你在等待操作完成时选择做其他不相关的事情。但是,您不是检查它,而是将检查工作委托给其他东西,可能是操作本身或监视器,并且您让那个东西通知并可能在响应可用时中断您,以便您可以继续进行需要它的其他操作。

这是一个奇怪的术语。没有什么意义,因为所有这些解决方案都是创建相关任务同步协调的方法。这就是为什么我更喜欢称它为事件。

所以这一次,你决定升级你的烤面包机,这样当烤面包完成时它就会发出哔哔声。你碰巧一直在听,甚至在你洗碗的时候。一听到哔哔声,你就在记忆中排队,一旦你洗完盘子,你就会停下来把黄油涂在吐司上。或者你可以选择暂停洗盘子,然后马上处理烤面包的事。

如果你听不到嘟嘟声,你可以让你的伴侣帮你看着烤面包机,并在烤好面包的时候告诉你。你的伴侣可以自己选择上述三种策略中的任何一种来协调它的任务,即看着烤面包机,并告诉你他们什么时候准备好了。

最后要注意的是,虽然非阻塞和异步(或者我更喜欢称之为事件)允许你在等待时做其他事情,但你没有太多。您可以选择不断循环检查非阻塞调用的状态,不做其他任何事情。这通常比阻塞更糟糕(比如看着烤面包机,然后离开,然后再回到它,直到它完成),所以很多非阻塞api允许您从它过渡到阻塞模式。对于事件,您可以等待空闲,直到收到通知。这种情况的缺点是,添加通知很复杂,而且可能成本很高。你必须买一个有哔哔声功能的新烤面包机,或者说服你的伴侣帮你看。

还有一件事,你需要意识到这三者之间的权衡。其中一个并不明显比其他的好。想想我举的例子。如果你的烤面包机这么快,你连洗碗的时间都没有,甚至都没开始洗,这就是你的烤面包机的速度。在这种情况下,开始做别的事情只是浪费时间和精力。阻塞就行了。同样,如果洗碗的时间比烤面包的时间长10倍。你得问问自己什么事情更重要?到那个时候,烤面包可能已经又冷又硬了,不值得这样做,堵住也可以。或者你应该在等待的时候选择更快的事情去做。还有更明显的,但我的答案已经很长了,我的观点是你需要考虑所有这些,以及实现每一个的复杂性,以决定是否值得,以及它是否真的会提高你的吞吐量或性能。

编辑:

虽然这已经很长了,但我也希望它是完整的,所以我再加两点。

  1. 通常还存在第四个模型,称为多路复用。这是指当您等待一个任务时,您启动另一个任务,当您等待两个任务时,您启动另一个任务,以此类推,直到您启动了许多任务,然后,您空闲地等待,但所有任务都启动了。因此,只要完成了任何一个,您就可以继续处理它的响应,然后返回等待其他响应。这被称为多路复用,因为当你等待的时候,你需要一个接一个地检查每个任务是否已经完成,直到其中一个完成。它是普通非阻塞之上的一种扩展。

在我们的例子中,这就像启动烤面包机,然后是洗碗机,然后是微波炉,等等。然后伺候他们中的任何一个。你会检查烤面包机,看看它是否做好了,如果没有,你会检查洗碗机,如果没有,你会检查微波炉,然后再次检查。

  1. 尽管我认为这是一个很大的错误,但同步通常用于表示一次只做一件事。并且在同一时间异步许多事情。因此,您将看到同步阻塞和非阻塞被用来指代阻塞和非阻塞。而异步阻塞和非阻塞用来指多路复用和事件化。

我真不明白我们是怎么走到这一步的。但是当涉及到IO和计算时,同步和异步通常指的是非重叠和重叠。也就是说,异步意味着IO和计算是重叠的,也就是并发发生的。而同步意味着它们不是,因此是顺序发生的。对于同步非阻塞,这意味着您不启动其他IO或计算,您只是忙碌地等待并模拟阻塞调用。我希望人们不要像这样误用同步和异步。所以我不鼓励。

Edit2:

我认为很多人都被我对同步和异步的定义搞糊涂了。让我试着说得更清楚一点。

同步被定义为以可预测的时间和/或顺序发生。意思是你知道某事什么时候开始和结束。

异步被定义为没有可预测的时间和/或顺序发生。意思是你不知道某事什么时候开始和结束。

这两种情况可以并行或并发发生,也可以依次发生。但是在同步情况下,您确切地知道事情将在什么时候发生,而在异步情况下,您不确定事情将在什么时候发生,但您仍然可以进行一些协调,至少保证一些事情只在其他事情发生之后发生(通过同步它的某些部分)。

因此,当您拥有异步流程时,异步编程允许您设置一些顺序保证,以便某些事情以正确的顺序发生,即使您不知道事情何时开始和结束。

举个例子,如果我们需要做A,那么B和C随时都可能发生。在顺序但异步的模型中,你可以有:

A -> B -> C
or
A -> C -> B
or
C -> A -> B

每次你运行这个程序,你都可以得到一个不同的,看起来是随机的。这仍然是顺序的,没有并行或并发的,但你不知道事情什么时候开始和结束,除非你让B总是发生在A之后。

如果你只添加了并发性(没有并行性),你也可以得到这样的东西:

A<start> -> C<start> -> A<end>   -> C<end>   -> B<start> -> B<end>
or
C<start> -> A<start> -> C<end>   -> A<end>   -> B<start> -> B<end>
or
A<start> -> A<end>   -> B<start> -> C<start> -> B<end>   -> C<end>
etc...

同样,你不知道事情什么时候开始和结束,但你已经让B总是在A结束后开始,但那不一定是在A结束后立即开始,它是在A结束后的某个未知时间,B可以完全或部分地发生在两者之间。

如果你添加了并行,现在你就有了这样的东西:

A<start> -> A<end>   -> B<start>       -> B<end>         ->
C<start> -> C<keeps going> -> C<keeps going> -> C<end>
or
A<start> -> A<end>         -> B<start> -> B<end>
C<start> -> C<keeps going> -> C<end>
etc...

现在,如果我们看看同步的情况,在顺序设置中,你会有:

A -> B -> C

这个顺序总是这样的,每次你运行程序,你会得到A, B, C,尽管从需求的概念上讲C可以在任何时候发生,在同步模型中你仍然可以准确地定义它开始和结束的时间。当然,你可以这样指定它:

C -> A -> B

相反,但由于它是同步的,那么这个顺序将是每次运行程序时的顺序,除非您再次更改代码以显式更改顺序。

现在如果你把并发添加到同步模型中,你可以得到:

C<start> -> A<start> -> C<end> -> A<end> -> B<start> -> B<end>

再说一次,不管你运行了多少次这个顺序都是这样的。类似地,你可以在代码中显式地更改它,但它在整个程序执行中是一致的。

最后,如果你将并行性也添加到同步模型中,你会得到:

A<start> -> A<end> -> B<start> -> B<end>
C<start> -> C<end>

同样,在运行的每个程序上都是如此。这里的一个重要方面是,要使其完全同步,这意味着B必须在A和C结束后开始。如果C是一个可以更快或更慢地完成的操作,这取决于机器的CPU功率,或其他性能考虑因素,要使它同步,你仍然需要让B等待它结束,否则你又会得到一个异步行为,其中不是所有的时间都是确定的。

在协调CPU操作和CPU时钟时,你会得到这种同步的东西,你必须确保你能在下一个时钟周期内及时完成每个操作,否则你需要再延迟一个时钟来为这个操作提供空间,如果你不这样做,你就会搞砸你的同步行为,如果事情依赖于这个顺序,它们就会中断。

最后,许多系统混合了同步和异步行为,因此如果您有任何固有的不可预测事件,比如用户何时单击按钮,或远程API何时返回响应,但您需要保证事物的顺序,那么基本上需要一种同步异步行为的方法,以便根据需要保证顺序和计时。我之前讲过一些同步策略,比如阻塞,非阻塞,异步,多路复用等等。看到“async”的重音了吧,这就是我所说的“令人困惑”这个词。有人决定把同步异步进程的策略称为“async”。这让人们错误地认为异步意味着并发,同步意味着顺序,或者在某种程度上阻塞是异步的对立面,而正如我刚刚解释的,同步和异步在现实中是一个不同的概念,它涉及到事物的时间同步(在某个共享时钟上或以可预测的顺序)或不同步(不在某个共享时钟上或以不可预测的顺序)。异步编程是同步两个本身是异步的事件(发生在不可预知的时间和/或顺序)的策略,为此我们需要添加一些保证它们可能发生的时间或至少以何种顺序发生。

因此,使用“异步”这个词,我们就剩下了两种情况。在他们:

  1. 异步进程:我们不知道它们将在什么时间开始和结束的进程,因此也不知道它们将以什么顺序结束运行。
  2. 异步编程:一种编程风格,它允许您使用回调或观察者来同步两个异步进程,这些回调或观察者会中断执行程序,以便让执行程序知道已经完成了一些事情,这样您就可以在进程之间添加可预测的顺序。

同步表示在另一个结果按顺序。之后开始一个

异步表示一起开始,结果上不保证顺序

阻塞表示导致阻塞执行下一步的内容。

非阻塞表示不等待任何东西而一直运行的东西,克服 阻塞

我敲门,等他们来开门。(我在这里无所事事)

非阻塞如:我敲门,如果他们立刻打开,我问候他们,走进去,等等。如果门没有立刻打开,我就去下一户人家敲门。(我正在做某事,没有闲着)

我只有在下雨的时候才会出去。(依赖关系存在)

我要出去。可能会下雨。(独立事件,发生时间无关紧要)

同步或异步,两者都可以是阻塞或非阻塞的,反之亦然

简单地说,

function sum(a,b){
return a+b;
}

为非阻塞。而异步则用于执行阻塞任务,然后返回阻塞任务的响应

同步 asynchonous
块I/O必须是同步I/O,因为它必须按顺序执行。同步I/O可能不是块I/O 不存在
无堵塞 非块和同步I/O同时是轮询/多路复用。 非块和异步I/O同时并行执行,如信号触发…
  • block/non-block描述了初始化实体本身的行为,它意味着实体在等待I/O完成期间所做的事情
  • 同步/异步描述了I/O初始化实体和I/O执行器(例如操作系统)之间的行为,它意味着这两个实体是否可以并行执行