企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## 概述 在网络上看到很多关于阻塞IO、非阻塞IO、同步IO、异步IO的例子,总觉得写的过于复杂并且让人难以理解。于是我把我自己把这几个概念的理解写下来以供参考。 ## POSIX POSIX(可移植操作系统接口)把同步IO操作定义为导致进程阻塞直到IO完成的操作,反之则是异步IO I/O 操作一般分为两个部分: * 阶段1:应用程序发起 I/O 操作请求,**等待数据**,或者将要操作的数据拷贝到系统内核中(比如 socket)。 * 阶段2:系统内核进行 I/O 操作(一般是内核将数据拷贝到用户进程中)。 ## 简述阻塞、非阻塞、同步、异步 ### 阻塞和非阻塞 阻塞和非阻塞发生在请求处,也就是**阶段1** ,关注的是程序在等待调用结果时的状态。 * 阻塞是指**调用结果**返回之前,当前进程(线程)会被挂起,调用进程(线程)会阻塞在IO请求处,直到IO操作请求完成,数据到来,最重要的是用户进程的函数在请求过程中不会返回。 * ![](http://image.euphie.net/2017-09-24-23-18-01.png) * eg:recv()函数默认是阻塞的,什么是阻塞呢?就是当你调用recv()函数时,整个进程或者线程就等待在这里了,直到你recv的fd的所有信息都被send过来,这么做好处就是保证所有信息都能够完整的读取了,但劣势也很明显,就是在recv()的过程中你的进程或线程做不了其它事情,由此,引入了非阻塞IO。 * 非阻塞调用是指在不能立刻得到结果之前,该调用不会阻塞进程(线程),进程(线程)可以去干别的事情,一般使用轮询的方法来查询IO操作的数据是否准备好了。 * ![](http://image.euphie.net/2017-09-24-23-19-53.png) * eg:以recv()函数为例,当你将其设置为非阻塞时,每次当你recv()时,就直接返回,不管信息有没有完全send进来,好处很明显,recv()了之后进程马上能处理下一行代码,坏处也很明显,就是你不知道你的消息是否读完了,这种问题就是TCP中大名鼎鼎的半包问题(解决办法主要是通过一个buffer缓存所有读进来的消息) * 当使用**I/O 多路复用**的时候,用户的 I/O 操作会立即返回,但会利用 select 和 epoll 等方法对所监视的 I/O 操作描述符进行遍历轮询(此操作是为了检查数据是否准备完毕,也就是 I/O 操作的**阶段1**,同时此操作是阻塞,进程或者线程需要等待轮询结果的返回),查看可用的句柄并返回。然后用户进程再对其进行操作。 ### 同步/异步IO * 同步和异步关注的是消息通信机制,具体来说就是调用者是否等待调用结果的返回,对于 I/O 操作而言,就是应用程序是否等待 I/O 操作完成。 * eg: 以recv()函数为例, 不管 是否设置为阻塞还是非阻塞(阶段1),都有一个将内核数据拷贝到用户进程的I/O操作(阶段2),如果用户进程等待I/O操作返回,则为同步,反之则为异步。 ### 同异步io和阻塞非阻塞io的区别 结合前面的概念,可以看出,其实这两者存在本质的区别,**它们的修饰对象是不同的**。 * 阻塞和非阻塞是指进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪。 * 同步和异步是指访问数据的机制,**同步一般指主动请求并等待 I/O 操作完毕的方式,当数据就绪后在读写的时候必须等待**,**异步则指主动请求数据后便可以继续处理其它任务,随后等待 I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。** >**只有同步的时候,才会有是否阻塞之说**。 ## IO多路复用 这里在调用`recv`前先调用`select`或者`poll`,这两个个系统调用都可以在内核准备好数据(网络数据到达内核)时告知用户进程,这个时候再调用`recv`一定是有数据的。因此这一过程中它是**阻塞于**`select`或`poll`,而没有阻塞于`recv`,有人将非阻塞IO定义成在读写操作时没有阻塞于系统调用的IO操作(不包括数据从内核复制到用户空间时的阻塞,因为这相对于网络IO来说确实很短暂),如果按这样理解,这种IO模型也能称之为非阻塞IO模型,但是按POSIX来看,它也是同步IO,称之为同步非阻塞IO。 这种IO模型比较特别,分个段。因为它能同时监听多个文件描述符(fd)。当有文件描述符有就绪事件时,则返回就绪事件的文件描述符来进行读写。 ![](http://image.euphie.net/2017-09-24-23-21-54.png) ## 异步 调用`aio_read`,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。整个过程没有`recv`,这才是真正的异步IO。 ![](http://image.euphie.net/2017-09-24-23-23-36.png) ## 信号驱动 通过调用sigaction注册信号函数,等内核数据准备好的时候系统中断当前程序,执行信号函数(在这里面调用recv)。是不是很像异步IO?很遗憾,它还是同步IO(省不了数据从内核拷贝到用户空间的操作)。 ![](http://image.euphie.net/2017-09-24-23-22-38.png) ## 总结比较下五种IO模型 ![](https://images2015.cnblogs.com/blog/1066890/201611/1066890-20161129014959615-1351089676.png) ## 总结 IO分两阶段: ``` 1.数据准备阶段 2.内核空间复制回用户进程缓冲区阶段 ``` 一般来讲:阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的(尽管时间很短)。只有异步IO模型是符合POSIX异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。 >ps:以上图片均截自UNIX网络编程卷1。