同步vs异步vs阻塞vs非阻塞
“同步”和“阻塞”的概念,只从字面上理解,很容易让人混淆。我之前也被这两组概念困扰了很久,趁着最近工作不太忙来捋一捋。
概念分析
同步与异步
同步和异步是针对进行某一操作被调用者的响应方式来说的。
同步操作时,调用者需要等待被调用者执行完毕并返回结果。比如你打电话给书店老板让他找一本书,你等着他找到后告诉你结果。
而异步操作,调用发出后就直接返回了,所以没有直接获得返回结果。被调用者执行完毕后,发出通知告知调用者。这次书店老板直接说“等我找到了,再打电话告诉你”,然后就挂了电话。
阻塞与非阻塞
阻塞和非阻塞关注的是调用者在等待被调方返回结果时的动作。
如果是阻塞调用,在收到返回结果前,调用方会被挂起等待,直到收到结果才能进行下一步。就好比你一直听着电话等书店老板告诉你结果,这段时间你不能去做别的事。
而非阻塞调用,在收到结果前,你可以去干别的事。还是上面的例子,你在老板告诉你结果前完全可以去干点别的事,当然也可以隔几分钟去问下老板有没有结果。
总结
这两组概念容易造成混淆,主要是因为都是“一个要等待,一个不用等待”。这样理解没用问题,但需要明确他们区别的关键在于动作的主体:同步异步关注的是被调方的处理逻辑,告知结果的时机;阻塞非阻塞关注的是调用者等待返回结果期间的状态。
Linux系统IO模型
了解了以上概念顺便再来学习一下Linux系统的IO模型。
前言
一次IO操作(以read为例),需要经历2个步骤:
- 等待数据准备
- 将数据从内核拷贝到用户进程中
相应的,Linux系统有以下五种IO模式。
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路复用( IO multiplexing)
- 信号驱动 I/O( signal driven IO)
- 异步 I/O(asynchronous IO)
1、阻塞IO
在Linux系统中,所有的socket默认都是blocking,当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。这个过程需要等待数据被拷贝到操作系统内核的缓冲区中。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
blocking IO的特点是在IO执行的两个阶段都被block了
2、非阻塞IO
当用户进程发出recvfrom调用时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个recvfrom操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是error时,它就知道数据还没有准备好,于是它可以再次调用recvfrom。一旦kernel准备好了数据,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
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多路复用机制,即可以监视多个描述符,一旦某个描述符就绪(读或写就绪),能够通知程序进行相应读写操作。
这个图和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
epoll
4、异步IO
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它收到到一个异步read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
参考资料
同步vs异步vs阻塞vs非阻塞