Skip to content
平兄聊技术
Go back

同步异步与阻塞非阻塞(上)

同步异步,阻塞非阻塞,这应该是一个老生常谈的话题了,然而也常常使人迷糊,毕竟从字面意思上来说,也确实是两组意思相近的反义词。最近我也仔细研究了一下。

阻塞(block) 非阻塞(nonblock),是指当一件事情还没有达到执行条件的时候,是等待条件达到后再执行(阻塞),还是先做别的事等这件事的条件成熟了再执行(非阻塞)。

同步(synchronous)异步(asynchronous),是指当一件事情内核做起来很费劲需要较长时间的时候,我是选择等待内核做完我再进行下一步(同步),还是我先去忙别的事情内核完成后通知我(异步)。

你看,上面两段描述,都有”等待“、”先去忙别的“这种字眼,无怪让人迷惑。下面,为了详细阐述这个问题,咱们先定义一下描述的场景。

在客户端连接服务器的场景中,Client 与 Server 已经建立了一条TCP连接,此时连接状态为 ESTABLISHED,但是还没有数据传输。此时,Server 在等待 Client 发送数据,即调用了ssize_t read(int, void*, size_t)。 为描述方便,也没有使用多路复用,也没有对这条连接的fd设置O_NONBLOCK.

那么我们就从这个read调用入手。

此时的read,其实包含了2个步骤:

  1. 等待数据的到来
  2. 数据到来后,等待内核将数据从内核空间拷贝到用户空间指定的地址。

图片

如图。这里的第一个步骤,说的就是阻塞、非阻塞这件事。如果设置了O_NONBLOCK,那么read操作由默认的阻塞,变为了非阻塞的。当没有数据可读时,该调用不是一直等待,而是立即返回了EAGAIN。若此时有数据可读,即开始进入第二步,同步拷贝。当数据量较小时,我们可能不太能感知到这一步操作,以为就像在内存中 int i = 1;一样瞬时完成。这也是会认为”同步=阻塞“,”异步=非阻塞“的主要原因。

一如我们了解到的操作系统知识,read调用者给出的地址,是一个用户态地址X,而操作系统收到了socket数据,是放在内核缓冲区的,需要把这个用户态地址,翻译成对应的某个物理地址Y,把数据从内核中的某个地址Z搬运到地址Y。不难想象,当数据量变大时,这也是非常费时间的。这个时间,就是我们为”同步“操作付出的成本。反之,如果是一个异步操作,则当前调用立即返回(不考虑阻塞行为),内核会在数据拷贝完成后或调用回调函数(如果调用时给出),或发出一个信号(比如signal(7))。

TL;DR

以上解释,我们有不少猜测。下面,我们去内核代码看看当ssize_t read(int, void*, size_t)调用发生在一个socket上时,是不是真的如我们猜测的那样。

咱们明天见。


Share this post on:


Previous Post
同步异步与阻塞非阻塞(下)
Next Post
那些常见却没人解释的tricks(一)