IO模型
背景
最近准备任职资格,肯定要聊设计和对应的技术方案,去年比较好的方案就是多线程异步IO抓图实现,赶紧复习一下。
参考文档
epoll:epoll原理解析
博文:
epoll的IO模型是同步异步
阻塞式I/0 和 非阻塞式I/O 同步异步详细介绍
IO模型
1. 同步(Synchronous):
• 同步IO操作意味着IO请求发出后,发起者必须等待操作完成才能继续执行后续代码。这意味着在IO操作正在进行时,程序会被挂起(等待),直到操作完成。
2. 异步(Asynchronous):
• 异步IO操作允许程序在发出IO请求后,继续执行后续代码,不必等待IO操作完成。通常,操作完成后,会通过某种形式的通知(例如回调函数)来告知发起者。
3. 阻塞(Blocking):
• 阻塞IO指的是IO操作导致请求的线程被挂起,直到有数据准备好(对于读操作)或者直到数据被成功写入(对于写操作)。在此期间,其他的IO操作或计算操作无法在该线程上执行。
4. 非阻塞(Non-blocking):
• 非阻塞IO指的是发起IO操作的请求会立即返回,不等待IO操作实际完成。如果操作因为数据未准备好而无法立即完成,调用会立即返回一个错误代码,通常是提示“资源暂时不可用”。非阻塞IO通常需要轮询(polling)来检查IO操作是否完成。
简单总结:
• 同步与异步关注的是IO操作的发起与完成之间的关系,是否等待IO操作完成来继续执行。
• 阻塞与非阻塞关注的是在等待IO操作完成时,线程的状态,是否继续执行其他任务。
同步阻塞式IO

同步阻塞式IO如上图,同步说明了应用线程(调用者)需要等待IO操作完成后才能执行其他任务、阻塞说明了调用者在IO返回前不能做其他事情,只能等待。
同步非阻塞式IO与升级版IO多路复用
同步非阻塞式IO

如网图,同步非阻塞IO,同步说明了应用线程(调用者)需要等待IO操作完成后才能执行其他任务,非阻塞说明说明线程不必死等IO返回,他可以做其他事情,通过轮询方式来获取响应。
这个方案会让人疑惑,因为它还不如同步阻塞式IO,反复轮询浪费CPU,确实,实际这种方案没人用,但是它是接下来升级方案的基础,所以得先理解。
升级版IO多路复用

如网图,当用户线程提交一个请求之后,专门的轮询线程会记录这个请求和对应套接字,并不断轮询套接字,如果有数据已经准备好,则调用回调函数处理数据,并将其交给对应用户线程去使用。
事实上对于轮询线程来说,它依旧是同步非阻塞的,但是对于用户线程来说,看起来一切都是异步的。
异步非阻塞IO

真正的异步非阻塞方案,当有响应时会由操作系统主动通知你,并把数据交给你。如网图,用户线程调用aio_read之后,立即返回了,去干别的了,当响应数据收到后,操作系统会将其从内核空间拷贝到用户空间,并告知用户线程数据准备好了,然后用户线程可以处理这些响应报文。
但是值得一提的是:
在java中虽然有aio,但是仅仅在windows系统中才使用iocp实现了真正的异步非阻塞IO,在linux中虽然调用epoll方法声称实现了异步非阻塞IO,但是其实2.6版本以下,依旧是使用的IO多路复用方案,在2.6以上epoll才是真正的异步非阻塞IO,但是对于用户线程来说没啥区别。
linux中支持select/poll/epoll方法,目前最好的还是epoll方法,底层实现详情可以参考:epoll:epoll原理解析
开发参考
理解了以上的模型,那我们就可以理解开发过程中的方案了:
-
httpClient的request-per-thread方案其实是同步阻塞IO,我们用线程数来控制http请求数量,而httpClient在内部实现时,在pending队列中,未拿到请求的线程会调用await休眠,在leased中队列中会按照同步阻塞IO方案休眠,直到响应结果返回,所以整个请求-响应的过程,用户线程一直在等待。
-
使用WebClient的异步非阻塞方案实际还是IO多路复用方案,开发中用户线程需要先定义回调函数,也就是拿到响应结果之后的执行逻辑,然后再发送请求,请求发送完之后用户线程就当个甩手掌柜去干别的了,如果请求回来了,轮询线程会主动取出数据,从等待队列中找到对应回调函数,然后交给它执行,这个负责回调函数执行线程就是在netty中定义的loop线程池(WebClient默认用netty实现异步框架)。
第一个方案在io等待时间比较长,io请求很多的场景下,明显会创建很多线程,而线程整个生命周期中实际分配到CPU时间片的时间很少,会浪费很多内存,CPU也会在频繁上下文切换中降低性能。但是WebClient的编码也会因为很多回调函数,比较难看懂,看起来很复杂。
浙公网安备 33010602011771号