同步vs异步vs阻塞vs非阻塞

“同步”和“阻塞”的概念,只从字面上理解,很容易让人混淆。我之前也被这两组概念困扰了很久,趁着最近工作不太忙来捋一捋。

概念分析

同步与异步

同步和异步是针对进行某一操作被调用者响应方式来说的。

同步操作时,调用者需要等待被调用者执行完毕并返回结果。比如你打电话给书店老板让他找一本书,你等着他找到后告诉你结果。

而异步操作,调用发出后就直接返回了,所以没有直接获得返回结果。被调用者执行完毕后,发出通知告知调用者。这次书店老板直接说“等我找到了,再打电话告诉你”,然后就挂了电话。

阻塞与非阻塞

阻塞和非阻塞关注的是调用者等待被调方返回结果时的动作。

如果是阻塞调用,在收到返回结果前,调用方会被挂起等待,直到收到结果才能进行下一步。就好比你一直听着电话等书店老板告诉你结果,这段时间你不能去做别的事。

而非阻塞调用,在收到结果前,你可以去干别的事。还是上面的例子,你在老板告诉你结果前完全可以去干点别的事,当然也可以隔几分钟去问下老板有没有结果。

总结

这两组概念容易造成混淆,主要是因为都是“一个要等待,一个不用等待”。这样理解没用问题,但需要明确他们区别的关键在于动作的主体同步异步关注的是被调方的处理逻辑,告知结果的时机;阻塞非阻塞关注的是调用者等待返回结果期间的状态。

Linux系统IO模型

了解了以上概念顺便再来学习一下Linux系统的IO模型。

前言

一次IO操作(以read为例),需要经历2个步骤:

  1. 等待数据准备
  2. 将数据从内核拷贝到用户进程中

相应的,Linux系统有以下五种IO模式。

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路复用( IO multiplexing)
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)

五种I/O模型

1、阻塞IO

在Linux系统中,所有的socket默认都是blocking,当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。这个过程需要等待数据被拷贝到操作系统内核的缓冲区中。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

阻塞IO

blocking IO的特点是在IO执行的两个阶段都被block

2、非阻塞IO

当用户进程发出recvfrom调用时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个recvfrom操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是error时,它就知道数据还没有准备好,于是它可以再次调用recvfrom。一旦kernel准备好了数据,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

非阻塞IO

nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。

3、IO多路复用

所谓的I/O复用,就是多个I/O可以复用一个进程。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

  • select:O(n)

用户进程每次传给内核一个用户空间分配的fd_set表示“关心的 socket”。其结构(相当于 bitset)限制了只能保存 1024 个 socket。每次 socket 状态变化,内核利用fd_set查询 O(1),就能知道用户进程是否关心这个 socket。内核是复用了fd_set作为出参,返还给用户进程。而用户进程必须遍历一遍 socket 数组 O(n),才知道哪些 socket 就绪了。

  • poll:O(n)

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的

  • epoll:O(1)

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。

select,poll,epoll都是IO多路复用机制,即可以监视多个描述符,一旦某个描述符就绪(读或写就绪),能够通知程序进行相应读写操作。

IO多路复用

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但多路复用的优势在于它可以同时处理多个connection。

所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

附:示例代码

  • select

    select

  • epoll

    epoll

4、异步IO

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它收到到一个异步read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

异步IO


参考资料

Linux IO模式及 select、poll、epoll详解

同步、异步和阻塞、非阻塞的区别

同步vs异步vs阻塞vs非阻塞

https://blog.luzy.top/posts/3997962891/

作者

江风引雨

发布于

2021-09-07

更新于

2023-01-11

许可协议

CC BY 4.0

评论