概述
IO模型:
- 同步阻塞IO
- 同步非阻塞IO
- IO多路复用
- 异步IO
BIO,NIO,AIO,Netty及Redis线程模型
1、BIO,NIO,AIO的区别?
2、什么是阻塞IO以及非阻塞IO?
3、Reactor和Proactor IO设计模式是什么?
4、NIO底层select、poll和epoll实现的区别 ?
5、Java NIO的几个核心组成部分是什么?作用分别是什么?
6、Redis、Netty、Tomcat的线程模型与NIO的联系是什么?
在1.4版本之前,Java IO类库是阻塞IO;从1.4版本开始,引进了新的异步IO库,被称为JavaNew IO类库,简称为JAVA NIO。New IO类库的目标,就是要让Java支持非阻塞IO,基于这个原因,更多的人喜欢称Java NIO为非阻塞IO(Non-Block IO),称“老的”阻塞式Java IO为OIO(Old IO)。总体上说,NIO弥补了原来面向流的OIO同步阻塞的不足,它为标准Java代码提供了高速的、面向缓冲区的IO。
Linux基础知识
用户空间和内核空间
现在操作系统都采用虚拟寻址,处理器先产生一个虚拟地址,通过地址翻译成物理地址(内存的地址),再通过总线的传递,最后处理器拿到某个物理地址返回的字节。
对 32 位操作系统而言,它的寻址空间(虚拟存储空间)为 4G(2 的 32 次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
针对 linux 操作系统而言:将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)供内核使用,称为内核空间。而将较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)供各个进程使用,称为用户空间。
直接I/O和缓存I/O
文件系统 IO 分为 DirectIO(直接I/O)和 BufferIO(缓存 I/O),BufferIO 也叫 Normal IO(标准 I/O)。大多数文件系统的默认 I/O 操作都是缓存 I/O。
读操作:操作系统检查内核的缓冲区有没有需要的数据,如果已经缓存,则直接从缓存中返回;否则从磁盘中读取,然后缓存在操作系统的缓存中。
写操作:将数据从用户空间复制到内核空间的缓存中。这时对用户程序来说写操作就已经完成,至于什么时候再写到磁盘中由操作系统决定,除非显示地调用sync同步命令。
以 write 为例,数据会先被拷贝进程缓冲区,在拷贝到操作系统内核的缓冲区中,然后才会写到存储设备中。
同步和异步
同步,所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。
简单来说,同步就是必须一件一件事做,等前一件做完才能做下一件事。
例如:B/S模式中的表单提交,具体过程是:客户端提交请求->等待服务器处理->处理完毕返回,在这个过程中客户端(浏览器)不能做其他事。
异步,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
通知调用者的三种方式:
- 状态:即调用者需要轮询监听被调用者的状态,效率会很低
- 通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能
- 回调:与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数
例如:B/S模式中的ajax请求,具体过程是:客户端发出ajax请求->服务端处理->处理完毕执行客户端回调,在客户端(浏览器)发出请求后,仍然可以做其他的事。
同步和异步的区别:
总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
同步和异步都是基于应用程序私操作系统处理 IO 事件所采用的方式,比如:
同步:是应用程序要直接参与 IO 读写的操作。
异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知。
同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待 IO 事件完成(阻塞 IO 事件或者通过轮询 IO 事件的方式)。
对于异步来说,所有的 IO 读写都交给操作系统。此时可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后会给应用程序一个通知。
阻塞和非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
阻塞与非阻塞:阻塞指的是用户空间程序的执行状态。
阻塞是指用户空间(调用线程)一直在等待,而不能干别的事情;
非阻塞是指用户空间(调用线程)拿到内核返回的状态值就返回自己的空间
阻塞和非阻塞是进程在访问数据时,数据是否准备就绪的一种处理方式,比如当数据没有准备就绪的时候。
阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
非阻塞:当进程访问数据缓冲区时,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。
同步/异步与阻塞/非阻塞
- 同步阻塞
效率最低,实际程序中就是未对fd设置O_NONBLOCK标志位的read/write操作; - 异步阻塞
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。如select 函数,假如传入的最后一个timeout参数为NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在这个select 调用处。 - 同步非阻塞
实际上是效率低下的,想象下你一边干别的事情一边看消息到了没有,如果把干别的事情和观察下载完成情况的位置看成是程序的两个操作的话,程序需要在这两种不同的行为之间来回切换,效率可想而知是低下的。
很多人会写阻塞的read/write 操作,但是别忘可以对fd设置O_NONBLOCK 标志位,这样就可以将同步操作变成非阻塞的。 - 异步非阻塞
效率更高,
因为等待下载完成是你(等待者)的事情,而通知你则是电脑(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。
并发和并行
并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。这种方式称之为并发(Concurrent)。
并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)
并发和并行的区别:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
所以它们最关键的点:是否是『同时』。
IO模型
对于一次 IO 访问,它会经历两个阶段:等待数据准备就绪 (Waiting for the data to be ready);将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。
读函数:分为等待系统可读和真正的读
写函数:分为等待网卡可以写和真正的写
等待就绪的阻塞是不使用 CPU 的,是在空等。真正的读写操作的阻塞是使用 CPU 的,真正在干活,而且这个过程非常快,属于 memory copy,宽带通常在 1GB/s 级别以上,基本不耗时。
以 socket.read() 为例子:
- BIO:如果 TCP RecvBuffer 里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据
- NIO:如果 TCP RecvBuffer 有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回 0,永远不会阻塞
- AIO(Async I/O) :不但等待就绪是非阻塞的,连数据从网卡到内存的过程也是异步的。
换句话说,BIO 里用户最关心“我要读”,NIO 里用户最关心"我可以读了",在 AIO 模型里用户更需要关注的是“读完了”。
NIO 一个重要的特点是:socket 主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的 I/O 操作是同步阻塞的(消耗 CPU 但性能非常高)。
同步阻塞IO
Blocking IO,同步IO是指用户空间的线程是主动发起IO请求的一方,内核空间是被动接受方。异步IO则反过来,是指系统内核是主动发起IO请求的一方,用户空间的线程是被动接受方。
同步非阻塞IO
IO多路复用
Reactor反应器设计模式,也叫异步阻塞IO,Java中的Selector选择器和Linux中的epoll都是这种模型。
异步和多线程区别
异步和多线程区别
异步是目的,多线程是实现这个目的一种方法。
多线程和异步操作的异同
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有时候就认为多线程和异步操作是等同概念。
异步操作的本质
所有的程序最终都会由计算机硬件来执行。硬盘、光驱、网卡、声卡、显卡的技术规格中都有明确DMA的模式指标。DMA,直接内存访问,即拥有DMA功能的硬件在和内存进行数据交换时可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS这样的单进程(无线程概念)系统中也同样可以发起异步的DMA操作。
线程的本质
线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
异步操作的优缺点
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少共享变量的数量),减少死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,且难以调试。
多线程的优缺点
多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担,并且线程间的共享变量可能造成死锁的出现。
异步与多线程,从辩证关系上来看,异步和多线程并不是一个同等关系,异步是目的,多线程只是我们实现异步的一个手段。什么是异步:异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回。实现异步可以采用多线程技术或则交给另外的进程来处理。
参考
问懵逼:直接IO、缓存IO、阻塞与同步进程线程、同步异步、阻塞非阻塞、并发并行