去年的时候以一篇比较尬的故事(同步、异步、阻塞、非阻塞那些事)的形式介绍了一下阻塞、非阻塞 I/O 与同步、异步 I/O的区别和联系,这次重新把知识点总结一下,这篇只留下干货,湿货继续看那篇故事。
从应用程序角度
根据应用程序是否阻塞自身运行,可以把 I/O 分为阻塞 I/O 和非阻塞 I/O。
- 所谓阻塞 I/O,是指应用程序在执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,不能执行其他任务。
- 所谓非阻塞 I/O,是指应用程序在执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务。
从系统角度
根据 I/O 响应的通知方式的不同,可以把文件 I/O 分为同步 I/O 和异步 I/O。
- 所谓同步 I/O,是指收到 I/O 请求后,系统不会立刻响应应用程序;等到处理完成,系统才会通过系统调用的方式,告诉应用程序 I/O 结果。
- 所谓异步 I/O,是指收到 I/O 请求后,系统会先告诉应用程序 I/O 请求已经收到,随后再去异步处理;等处理完成后,系统再通过事件通知的方式,告诉应用程序结果。
总结
阻塞 / 非阻塞和同步 / 异步,其实就是两个不同角度的 I/O 划分方式。它们描述的对象也不同:
- 阻塞 / 非阻塞针对的是 I/O 调用者(即应用程序)
- 同步 / 异步针对的是 I/O 执行者(即系统)
举例
比如在 Linux I/O 调用中:
- 系统调用
read
是同步读,所以,在没有得到磁盘数据前,read
不会响应应用程序。 - 而
aio_read
是异步读,系统收到AIO
读请求后不等处理就返回了,而具体的read
结果,再通过回调异步通知应用程序。
再如,在网络套接字的接口中:
- 使用
send()
直接向套接字发送数据时,如果套接字没有设置O_NONBLOCK
标识,那么send()
操作就会一直阻塞,当前线程也没法去做其他事情。 - 当然,如果你用了
epoll
,系统会告诉你这个套接字的状态,那就可以用非阻塞的方式使用。当这个套接字不可写的时候,你可以去做其他事情,比如读写其他套接字。