同步与异步
工作形式
同步:正在进行的两个任务相互依赖,其中一个任务完成了,也需要等被依赖的另外一个任务完成才能够继续进行或者完成。这两个任务将具有同步性,要么同时完成要么同时失败。
异步:两个正在进行的相互依赖任务,其中一个任务如果完成了,那么它就算整个任务完成了而不需要等待另一个任务一起完成。这两个任务无法对对方进行“监测”。
消息通知
如果现在正在进行的任务是以同步的方式进行的,那么调用者就一定要一直等待回调的通知才能继续进行任务,等不到通知将会一直等待下去。
阻塞与非阻塞
工作形式
阻塞:再工作任务等待回调结果之前,什么任务也不能做,只能继续等待
非阻塞:在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回
对比
非阻塞的方式可以明显的提高CPU的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的CPU执行时间能不能补偿系统的切换成本需要好好评估。
四种实现方式
同步阻塞
效率是最低的,在等待的同时任何事情都不能干
同步非阻塞
在等待时仍可以干其他的事情,但是要一直切换线程来查看任务是否完成,消耗了资源
异步阻塞
在这个情况下,可以处理信息,但是如果到要回调结果时没有拿到结果就会一直卡住,实际上也是堵塞了
异步非阻塞
这种情况下工作线程可以不用等待结果也可以一直干其他的事情,效率是最高的
IO读写原理
在Java层面的应用开发或者是linux系统底层开发,都属于输入input和输出output的处理,简称为IO读写。
linux IO 存储栈分为7层:
VFS 虚拟文件层: 在各个具体的文件系统上建立一个抽象层,屏蔽不同文件系统的差异。
PageCache 层: 为了缓解内核与磁盘速度的巨大差异。
映射层 Mapping Layer: 内核必须从块设备上读取数据,Mapping layer 要确定在物理设备上的位置。
通用块层: 通用块层处理来自系统其他组件发出的块设备请求。包含了块设备操作的一些通用函数和数据结构。
IO 调度层: IO 调度层主要是为了减少磁盘IO 的次数,增大磁盘整体的吞吐量,队列中多个bio 进行排序和合并。
块设备驱动层: 每一类设备都有其驱动程序,负责设备的读写。
物理设备层: 物理设备层有 HDD,SSD,Nvme 等磁盘设备。
因为这七层架构,我们就发现一件事情。不管是进行read操作或者write操作,都不是从物理设备上直接读取进内存或者将数据直接写入物理设备中。
read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。
用户进程缓冲区和内核缓冲区
缓冲区的目的,是为了减少频繁的系统IO调用。大家都知道,系统调用需要保存之前的进程数据和状态等信息,而结束调用之后回来还需要恢复之前的信息,为了减少这种损耗时间、也损耗性能的系统调用,于是出现了缓冲区。
有了缓冲区,操作系统使用read函数把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区中。等待缓冲区达到一定数量的时候,再进行IO的调用,提升性能。至于什么时候读取和存储则由内核来决定,用户程序不需要关心。
进程缓冲区
在有些存取文件的时候,会生成内存数组,称之为buffer。然后每次调用read,读取设定字节长度的数据,写入buffer。其实在用户进程中设计缓冲区,主要是为了减少对底层系统的IO读写调用,降低上层与下层切换状态的时间。
内核缓冲区
一个用户进程要从磁盘读取数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程缓冲区中。
但若是内核缓冲区中没有数据,内核会把对数据块的请求,加入到请求队列,然后把进程挂起,为其它进程提供服务。
等到数据已经读取到内核缓冲区时,把内核缓冲区中的数据读取到用户进程中,才会通知进程。
os可能会把缓冲区积累到一个量之后再写入,这就是为什么有时候突然断电会导致数据丢失。
读与写(write,read)
读
网络或者用户发起请求,执行read()操作,将上下文由user space切换至kernel space
将内核缓冲区中的内容利用DMA将数据复制进用户缓冲区中
CPU将找到的内容向上传递复制进用户缓冲区
kernel切换回user space,read()回调返回
写
发起请求,执行write(),将上下文由user space切换至kernel space
CPU将用户缓冲区的内容复制进内核缓存区中
CPU利用DMA将内核缓冲区的内容复制进磁盘中
kernel切换回user space,write()回调返回
优化技术
页缓存技术
这个技术是用来减少对磁盘进行IO操作的,它将数据以页为单位进行读写,分为两个部分
读
进行read操作时,如果页中间有要读的缓存数据,就不会去访问磁盘直接从缓冲区拿数据
如果找不到要读取的这页内容,则会去磁盘寻找数据,找到数据并加入缓冲区的同时会将后几页的数据一起加入缓冲区中。
写
当进程发起 write 系统调用写数据到文件中,先写到页缓存,然后方法返回。此时数据还没有真正的保存到文件中去,Linux 仅仅将页缓存中的这一页数据标记为 “脏”,并且被加入到脏页链表中。
然后,由 flusher 回写线程周期性将脏页链表中的页写到磁盘,让磁盘中的数据和内存中保持一致,最后清理“脏”标识。在以下三种情况下,脏页会被写回磁盘:
空闲内存低于一个特定阈值。
脏页在内存中驻留超过一个特定的阈值时。
当用户进程调用 sync() 和 fsync() 系统调用时。
IO模型
阻塞IO:发起IO调用,若不具备IO条件,则等待IO条件具备。具备则数据拷贝完毕后返回。一直等待资源浪费。
非阻塞IO:发起IO调用,若不具备条件则立即报错返回。通常是循环发起调用,若具备IO条件,则拷贝数据完毕后返回。不够实时。
信号驱动IO:先定义IO信号处理方式,若IO条件具备,直接信号通知进程,发起调用,拷贝数据后返回。比较实时。但流程控制较难。也是一种异步,因为拷贝就是异步的。
异步IO:定义信号处理,发起异步IO调用,自己直接返回,之后让别人等待条件具备(等待和数据拷贝都不用自己完成,进程或线程完成)“事情由别人干,干完通知我”。
IO条件具备后,数据拷贝由别人完成,信号通知进程:IO已经完成。可以对数据直接进行操作。(AIO)
多路转接IO:一种IO事件监控。同时对大量的描述符进行事件(描述符的可读/可写/异常)默认阻塞监控,监控描述符是否具备IO条件。
如果具备(就绪时)进行返回,对就绪的IO进行操作。是高并发的处理模型。