在开始讨论之前,展开几个概念
阻塞和非阻塞
- 阻塞:函数调用时,无法立刻得到返回值,当前进程被挂起
- 非阻塞:函数调用时,立刻获得返回值,无论是一个有效值、空值还是一个错误值
同步和异步
- 同步:函数调用时,无法立刻得到返回值/有效值,当前进程可以被挂起
- 异步:函数调用时,无法立刻得到返回值。等到被调用者处理完毕后,调用结果以回调的方式返回或处理
在很多时候,同步/异步和阻塞/非阻塞,这两个概念可以看作是相似的。但在 系统层面的IO 调用上,异步IO和非阻塞IO在对结果返回的处理上存在些许差别,但它们都属于非阻塞调用(Non Blocing System Call)
把它们组合在一起
- 同步阻塞:函数调用时,无法立刻得到返回值,当前进程被挂起,直到函数返回
- 同步非阻塞:每隔一段时间,就进行一次函数调用,直到获得一个有效值为止
- 异步非阻塞:函数调用时,立刻返回一个无效值。等到被调用者处理完毕后,调用结果以回调的方式返回或处理
IO模型
- (同步)阻塞IO
阻塞IO是Linux系统下默认IO模型。程序在读写时,会进行一次系统调用,将当前系统上下文交给系统内核处理,而自身被阻塞,进入等待环节。
- (同步)非阻塞IO
在Linux下,程序在系统调用读写操作时,会立刻获得一个返回值。但内核操作可能并未成功,数据还没有被读取/写入。程序会一直循问系统内核,直到得到I/O操作成功的返回值。
- 信号驱动IO (异步非阻塞)
程序调用读写操作时,并不等待系统内核的返回,而是直接处理其他任务。当系统内核执行完毕时,会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
- IO多路复用 (异步阻塞)
这个词是如此常见,你可以在搜索Redis的文章中看到它,也可以在Nodejs中看到它,更不用说Nginx、Python异步编程中见到它的身影。这些文章通常都有(或相似的意思)一句话,“XXX拥有如此惊人的高并发量,得益于IO多路复用模型”,那么到底这玩意是个啥呢?
程序调用读写操作时,虽然并不等待系统内核的返回,而是返回一个调用函数,但应用程序会被像select、poll或epoll等具有复用多个文件描述符的函数阻塞住,直到系统真的有结果返回了,然后通知程序,执行刚才返回的调用函数。看到这里,大家会充满疑惑,这不是阻塞IO差不多吗,为什么说IO多路复用模型效率会高很多呢?
多路复用(I/O multiplexing)指的其实是在单个线程通过记录跟踪每一个Socket(I/O流)的状态,来同时管理多个I/O流。 发明它的原因,是尽量多的提高服务器的吞吐能力。“复用”指的是使用一个线程,“多路”指的是多个Socket连接。使用多路复用模型的程序,可以单独开启一个线程用于程序IO操作,因此会比挂起线程的阻塞IO模型要节省资源。
select/poll模式
程序调用select/poll后被阻塞,当I/O流事件产生的时候,系统就会去唤醒进程去处理。但程序只知道有I/O流事件产生了,但并不知道是那一条Soket连接,所以它会轮询所有Soket连接,来确定是哪一条产生了I/O流事件。
epoll模式
在epoll模式下,I/O流事件产生后,进程会得知是哪一个Soket连接产生了事件,相比select/poll的轮询机制,这种方式显然更加合理。