简单谈谈IO模型这件事,大佬路上的必备知识。
最近越来越发现,IO操作真的是非常重要,甚至影响了程序的性能,比如自己写的LOG频繁的读写IO。通过使用IO复用,都可以避免使用多线程阻塞的方式实现的东西。
所以今天简单梳理下,因为也是初次总结,不好之处多多包涵。
什么是IO?
这个问题,简单说I指的是input,而O则是output,非常直观的意思就是计算机的输入、输出,描述的是计算机的数据流动的过程。输入/输出是主存和外部设备(如磁盘驱动器、终端、网络)之间拷贝数据的过程。
从操作系统角度来说,IO分为IO调用和IO执行,其中IO调用是由进程发起的,IO执行则是由操作系统来完成。IO调用的目的是将进程的内部数据迁移到外部(输出),或将外部数据迁移到进程内部(输入)。
那么一个进程调用一次外部输入类IO会进行那些步骤呢?
1.进程向操作系统发出请求外部数据
2.操作系统将外部数据加载到内核缓冲区
3.操作系统将数据从内核缓冲区拷贝到进程缓冲区
4.进程读取数据继续后续工作
附一张socket的传输过程图片。
针对IO调用(应用进程做的事情)分为阻塞和非阻塞。针对IO执行(操作系统做的事)分为异步和同步。
阻塞IO & 非阻塞IO
阻塞IO指的是,应用进程在请求IO之后,一直等待操作系统的返回,此时应用程序会一直停在请求的位置。
非阻塞IO指的是,应用程序在请求IO之后,不等待操作系统的返回,应用程序直接就返回做其他事情。
单纯的对比两种方式,我们发现好像非阻塞IO好像更好一点,不一直等待,直接返回做其他事情,其实不然。如果我们想要获得读取IO的结果,我们就不得查询它好没好,如果一直使用轮询的方式,其实更加浪费CPU的时间。反而不如使用多线程,使用其中一个线程用来阻塞IO获取数据。
但一个应用程序需要调用多个IO怎么办呢?
不能在一个线程中均阻塞,因为不知道那个IO先被操作系统执行完毕。
开多个线程每个线程都阻塞吗?不是很现实,假如有10几个,光是在线程间切换便浪费不少时间。
那可不可以使用非阻塞IO轮询方式查询?可以考虑,虽然浪费CPU时间,但操作IO比较多还是可以考虑。但我们还有更好的方法,那便是异步IO(asynchronous I/O)。
异步IO & 同步IO
同步IO是应用程序必须等待内核把IO操作处理完成后才会返回。而异步IO则不需等待IO操作完成,而是向内核发起一个操作立刻返回,当内核完成IO操作后,会通过信号的方式通知应用程序。
LINUX IO总共有5种:
同步IO (synchronous IO)
异步IO(asynchronous IO)
阻塞IO(bloking IO)
非阻塞IO(non-blocking IO)
多路复用IO(multiplexing IO)
信号驱动IO(signal-driven IO)
学习这五种IO的操作是很有必要的,比如最近我用的非阻塞IO+多路复用IO,实现了socket 服务端,可以通过epoll IO复用,接收多个客户端连接通信。
另外为了减少访问IO的次数,日志文件也会对IO访问进行控制,并不是简单的产生一条消息就通过IO写入到文件系统,而是先放入缓冲区,等缓冲区写满再往文件系统写的。
另外一般写定时器的时候也是,通过多路复用epoll去监视多个定时器的,而不是一直在阻塞着等待。
通过我的亲身经历告诉大家,这五种IO是必须要学习的,而且还能扩展你的编码思路,让你无拘无束的编码。
#End 2022/4/14 - 21:28