理解操作系统里阻塞/非阻塞、异步/非异步

阻塞/非阻塞 与 同步/异步

在开始前,首先要了解阻塞/非阻塞、同步/异步这 2 组概念的区别。

对于阻塞/非阻塞:说的是程序等待结果时的状态。阻塞是结果返回前,程序会挂起;非阻塞是是程序不会一直挂起,先去做别的事情。

对于同步/异步:在操作系统处理 I/O 的时候,阻塞和非阻塞都是同步的 I/O。只有少数的 API 才是异步 I/O。

007S8ZIlgy1gh6n4kqwupj30bj046aae.jpg

对于这 2 组概念时,要注意讨论的上下文。以 nodejs 为例,对于开发者来说,它是 非阻塞+异步 编程模型;对于 nodejs 实现来说,它底层是以 非阻塞+同步 的 epoll 来实现的。

知乎上有关于这个问题的讨论:

注意下有些高赞答案不一定是对的。

Reactor 与 Proactor

通俗来讲,对于高性能服务器来说,它首先要保证是非阻塞的,即:调用外部资源,不管结果如何,不会阻挡线程继续进行。

如果底层接口是同步的,那么数据已经被读到内核缓存,需要我们主动将其从内核缓存读到用户空间(进程空间);如果底层接口是异步的,那么数据到内核缓存,再到进程空间,我们可以直接取到数据。

Reactor 就是非阻塞+同步网络模型,Proactor 就是非阻塞+异步网络模型。理论上看,肯定 Proactor 效率更高,但是 Linux 下的 AIO 不完善,Reactor 更加通用。

通俗来讲(请自动对应内核缓存和用户空间):

  • Reactor:拿号,某个柜台空闲了就通知我去取款,我还是必须坐到柜台前取款,取款过程还是同步的。
  • Proactor:拿号,告诉大堂经理我要取款,款到了,大唐经理送到我手中,取款过程是异步的。

除此之外,高性能服务器需要创建线程/进程池,保证复用。

再看阻塞/非阻塞 和 同步/异步

在前面说明的基础上,会发现:阻塞/非阻塞说的是在调用外部资源时,线程是否能继续执行,无需等待。如果可以,那么就是非阻塞。

在非阻塞模型下,当调用完成时,线程会收到“通知”。如果底层用的是同步 IO,线程接到“通知”还在自己调用接口(例如 read() )去取出数据;如果底层用的是异步 IO,线程接到的“通知”就是数据,数据已经被操作系统取到了用户空间。

其实没那么难,只是容易被误导。

如果你想看示例代码,请参考:彻底理解:阻塞、非阻塞、同步、异步、Reactor、Proactor