首先从字眼上去理解I/O 其实就输入和输出的缩写,在计算机上的IO其实是指:
计算机中的一个程序和运行时数据是驻留在内存中,由CPU来执行调度,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口(所以一般网络上常说的IO都是指这个磁盘IO和网络IO)
数据流的分类:
如JAVA中常见的一些输入输出流:
在unix的操作系统,一切皆文件,所谓的文件其实就是指:一串二进制流。对应复杂操作系统中引入了文件描述符来绑定相关的IO的操作。
PS:文件描述符是由无符号整数表示的句柄,进程使用它来标识打开的文件。文件描述符与包括相关信息(如文件的打开模式、文件的位置类型、文件的初始类型等)的文件对象相关联,这些信息被称作文件的上下文。
在UNIX、Linux的系统调用中,大量的系统调用都是依赖于文件描述符。所以在某种程度是对文件描述符fd的操作就是数据流的操作。
具体的进程和文件描述的关系:可以参考:https://blog.csdn.net/qq_40772498/article/details/108782099
因Cpu和内存处理的数据的程度远高于我们的其他外设的速度,如磁盘速度,或其他网络IO数据的读取。因为速度的不匹配的问题,所以为了高效的处理多任务就是是并发的执行任务,索引引入了同步IO和异步IO:
PS:IO操作需要等待数据过程中阻塞了当前线程,导致其他代码无法执行,此时我们可以引入多线程或多进程并发执行任务,为多个用户进行服务。各自的线程和进程之间的相互不影响,各自的线程如遇到IO导致线程被挂起,其他用户的线程不受影响。
多线程和多进程模式的缺点:
1:不能无限新增线程和进程。 2:多线程和多进程多的情况增加系统开销,需要开辟更多的虚拟空间 3:进程和线程的上下文的切换所需的系统开销大。
PS:异步IO模型需要一个消息循环,在消息循环中,主线程会不断重复进行“读取消息-处理消息”的处理。
异步IO模型下,一个线程就可以同时处理多个IO请求。因为线程不会因为IO阻塞而挂起!
1.阻塞/非阻塞, 它们是程序的一种运行状态,是关于程序在等待消息(无所谓同步或者异步)时的状态描述,它是程序(线程)【等待关注消息】通知时的状态角度来说。
2.同步/异步,是程序【获得关注消息】通知的机制,与消息的通知机制有关的一种描述;
3.同步和异步是个线程处理方式或手段,他们区别是遇到IO请求是否等待。
从框架的角度是定义同步和异步:
- flask,bottle,fanlo 是一个同步框架
- FastAPI,Quart,Sanic,Vibora,Tornado····等即支持同步也支持异步
异步操作是可以被阻塞住的:异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,
而是在等待消息通知时被阻塞。
4.阻塞和非阻塞是线程的一种状态,线程要么处于阻塞,要么处于非阻塞,两者并不相同也并不冲突,它们的主要区别是:数据没准备好的情况下是否立即返回在某个时刻。
引起进程阻塞的事件可以有多种多样,比如:
等待I/O完成 申请缓冲区不能满足 等待信件(信号)等····
无论哪个池,其实都是为了在减少创建和销毁的操作的系统。
如:线程池-减少创建和销毁线程频率,维持合理数量的线程数,并让空闲的线程重新承担新的执行任务,提供线程复用,而不是新建。
如:连接池”也是对已存在的连接进行重用,维持相关连接的缓存池,减少创建和关闭连接的频率。
PS:在socket套接字可以通过为套接字的描述符属性设置非阻塞式,可以使用此功能.
和非阻塞模式类似,只是轮询的方式不再是使用用户线程,而是由内核轮询,由内核程序箭头所需要的数据,当数据准备好后,则调用内核函数复制数据到用户空间。
select只负责等,recvfrom只负责拷贝。各司其职!
PS:复用IO的基本思路就是通过slect或poll、epoll 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。
在网络IO中:
多路复用IO主要用于处理多个IO连接时候的场景。在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。
多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
如果使用:在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。
信号驱动式I/O是指进程预先告知内核,使得当某个描述符上发生某事时,内核使用信号通知相关进程。
信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,信号处理程序对SIGIO信号进行捕捉,并且发起系统调用来获取数据。
网络IO中:信号驱动IO模型,下当用户线程发起一个IO请求操作,会分配对应的socket一个信号函数,然后用户线程会继续执行,不会挂起,另一方面,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
PS:这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么事情(暂时还不理解这些地方,后续加深理解一下)
PS:在unix中异步IO函数以aio_或lio_开头
windows下应该是:IOCP是一个Win32 机制,方便于带有控制并发的服务器的并发执行(这个还真不了解,网上有人是这么一说,待了解!)
当应用程序调用aio_read时,(发起一个用户线程执行read),内核一方面取数据报内容返回,另一方面将程序控制权还给应用进程,应用进程继续处理其他事情,是一种非阻塞的状态(不会对用户线程产生任何阻塞,用户线程不会挂起)
内核等待数据准备完成—然后拷贝数据到用户进程内的缓冲区后,内核会给用户线程发送一个信号,告诉它(用户线程)read操作完成了。
PS:也就说异步IO模型中:用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个readi请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了(通过函数调用的方式进行数据处理)。
PS:异步IO依赖信号处理程序来进行通知,(但是通知是IO数据处理结果完成,而不是通知IO数据准备完成)
异步IO是真正的非阻塞,主进程,只需要负责分配IO工作,然后等待IO结果出来完成(数据已经从内核缓存区复制到用户空间内的应用程序的缓冲区)时通过回调函数对数据进行处理。
阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。
前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,
无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞
IO概念和五种IO模型:https://www.cnblogs.com/shengguorui/p/11949282.html
python并发编程之IO模型 :https://www.cnblogs.com/haiyan123/p/7465486.html
五种IO模型详解及优缺点 :https://blog.csdn.net/haitaobiyao/article/details/107533649
个人其他博客地址
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
小钟同学 | 文 【原创】| QQ:308711822
1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。 2:关于文章内容,有部分内容参考自互联网,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。
原文作者:程序员小钟同学;如有侵权,请联系管理员删除。