Tag Archives: non-blocking

关于同步/异步 VS 阻塞/非阻塞的一点体会

长期以来,同步(synchronous),异步(asynchronous),阻塞(blocking)和非阻塞(non-blocking)这几个概念一直困扰着我。我以前一直简单地以为同步等于阻塞,异步等于非阻塞。直到最近读完UNIX Network Programming (Volume 1)的第6章之后,并查阅了大量网上的资料之后,我才对这个问题有了一个比较清楚的认识。

在讨论这四个概念的区别之前,我们首先要确定一下我们讨论的上下文(context),那就是Linux的network IO。对于一个网络IO来说(以read作为例子),其执行过程通常可以分为两个阶段。第一阶段,等待数据从网络中到达,并被拷贝到内核中某个缓冲区(Waiting for the data to be ready)。第二阶段,把数据从内核态的缓冲区拷贝到用户态的应用进程缓冲区来(Copying the data from the kernel to the process)。

1. 阻塞/非阻塞 IO
根据一个高票的知乎回答,阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

在Linux下,一个socket的文件描述符(file descriptor)默认就是阻塞模式的。在这种模式下,即便这个socket压根没有收到任何数据,我们的read调用也会一直阻塞在那里,无法返回,直到有数据到达为止。

如果我们把这个socket的文件描述符用fcntl设置为非阻塞的。在这种模式下,如果这个socket没有收到任何数据,我们的read调用会立刻返回一个错误。这个时候,我们的程序就知道目前没法从这个socket里读到数据了,索性去干点别的事情,过段时间再调用read。当一个应用进程对一个非阻塞的文件描述符循环调用read时,我们称之为轮询(polling)。

现在再让我们回想网络IO的两个阶段,阻塞和非阻塞主要区别其实是在第一阶段等待数据的时候但是在第二阶段,阻塞和非阻塞其实是没有区别的。程序必须等待内核把收到的数据复制到进程缓冲区来。换句话说,非阻塞也不是真的一点都不”阻塞”,只是在不能立刻得到结果的时候不会傻乎乎地等在那里而已。

2. 同步/异步 IO
对于这两个东西,POSIX其实是有官方定义的。
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;
根据这个定义,不管是blocking IO还是non-blocking IO,其实都是synchronous IO。因为它们一定都会阻塞在第二阶段拷贝数据那里。

3. IO复用
这时候有些人会问了,IO复用(multiplexing)算是什么类型的IO呀。不同于这篇IBM的文章的观点,我个人认为,IO复用是阻塞同步IO

跟传统的阻塞IO不同,IO复用可以阻塞在多个socket文件描述符上。当其中任何一个socket有数据可读的时候(或者超时),IO复用的函数(select, poll,epoll)才会返回。然后进程可以逐一处理可读的socket文件描述符。

4. 真正的异步IO
对此我只知道Linux AIO。可惜本人才疏学浅,并没有Linux AIO的实际开发经验,在此就不详细介绍了,有兴趣的读者可以自己去尝试。

参考资料:
https://www.zhihu.com/question/19732473/answer/20851256
http://lifeofzjs.com/blog/2014/03/29/sycron-vs-block/
http://blog.csdn.net/historyasamirror/article/details/5778378