目录
1. 基本概念
- 同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。就是我调用一个功能,该功能没有结束前,我死等结果。
- 异步:当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)。
- 阻塞:阻塞调用函数是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回。也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 例如,我们在socket 中调用 recv 函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。
- 非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。通过 select 或 poll 或 epoll 通知调用者。
同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。
阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。
2. 五大 I/O 模型
阻塞 I/O(blocking I/O),非阻塞 I/O (nonblocking I/O),I/O 复用(I/O multiplexing)、信号驱动I/O (signal driven I/O (SIGIO))、异步I/O (asynchronous I/O)。前4种是同步的后一种的异步的。
首先明白一个输入操作通常包含两个过程:等待数据准备好,从内核向进程复制数据。对于套接字上的输入操作,第一步涉及等待数据从网络中到达,被复制到内核中的某个缓冲区;第二步就是从内核缓冲区拷贝进应用进程缓冲区。
2.1 阻塞 I/O
进程会一直阻塞,直到数据拷贝完成。应用程序调用一个 IO 函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好,一直等待….数据准备好了,从内核拷贝到用户空间,IO 函数返回成功指示。
阻塞I/O模型图:在调用recv()/recvfrom() 函数时,发生在内核中等待数据和复制数据的过程。
2.2 非阻塞 I/O 模型
非阻塞IO通过进程反复调用IO函数(轮询,多次系统调用,并马上返回)。我们把一个SOCKET 接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误(EWOULDBLOCK)。这样我们的 I/O 操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
一个非阻塞模式套接字多次调用 recv() 函数的过程。前三次调用 recv() 函数时,内核数据还没有准备好。因此,该函数立即返回 EWOULDBLOCK 错误代码。第四次调用 recv() 函数时,数据已经准备好,被复制到应用程序的缓冲区中,recv() 函数返回成功指示,应用程序开始处理数据。
2.3 I/O 复用模型
能实现同时对多个IO端口进行监听。调用阻塞在系统调用selcte上,而不是在 recv 真正的 IO 调用上面,内核数据准备好了,通知应用程序,然后调用 recv 将数据从内核空间拷贝到用户空间处理。
2.4 信号驱动式 I/O 模型
使用信号,让内核在描述符准备就绪的时候发送 SIGIO 信号通知我们,然后在信号回调函数里面,将数据拷贝到用户空间。
这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知。
2.5 异步 I/O 模型
告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与信号驱动模型的主要区别在于信号驱动式 I/O 是由内核通知我们何时可以启动一个 I/O 操作,而异步 I/O 模型是由内核通知我们 I/O 操作何时完成。
3. 5大 I/O 模型比较
前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞于 recvfrom 调用。相反,异步 I/O 模型在这两个阶段都要处理,从而不同于其他4种模型。
前4种模型一一阻塞式 I/O 模型、非阻塞式 I/O 模型、 I/O 复用模型和信号驱动式 I/O 模型都是同步 I/O 模型,因为其中真正的 I/O 操作 (recvfrom) 将阻塞进程。只有异步 I/O 模型与 posrx定义的异步 I/O 相匹配。