异步 I/O 模型
概念简介
异步 I/O 模型总体可以分为两种:反应式( Reactive)模型和前摄式(Proactive)模型.
传统的 select / epoll / kqueue 模型,以及 Java NIO 模型,都是典型的反应式模型
,即应用代码对 I/O 描述符进行注册,然后等待 I/O 事件。
当某个或某些 I/O 描述符所对应的 I/O 设备上产生 I/O 事件(可读、可写、异常等)时,系统将发出通知,于是应用便有机会进行 I/O 操作并避免阻塞。
由于在反应式模型中应用代码需要根据相应的事件类型采取不同的动作,最常见的结构便是嵌套的if {...} else {...}
或 switch
,并常常需要结合状态机来完成复杂的逻辑。
前摄式模型
则恰恰相反。在前摄式模型中,应用代码主动地投递异步操作而不管 I/O 设备当前是否可读或可写。投递的异步 I/O 操作被系统接管,应用代码也并不阻塞在该操作上,而是指定一个回调函数并继续自己的应用逻辑。当该异步操作完成时,系统将发起通知并调用应用代码指定的回调函数。
在前摄式模型中,程序逻辑由各个回调函数串联起来:异步操作 A 的回调发起异步操作 B ,B 的回调再发起异步操作 C ,以此往复。
Reactor 和 Proactor 同为事件驱动 I/O 模型
,其本质区别
在于事件触发时机: Reactor 在 I/O 设备就绪,即可以立即执行 I/O 调用而无需阻塞时触发,只有这时才可以放心大胆的执行 I/O 调用;而 Proactor 则允许在任意时刻发起 I/O 调用请求,并在 I/O 调用完成时触发事件。
I/O发生时涉及的对象和阶段
对于一个network I/O (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个I/O的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
1、 等待数据准备 (Waiting for the data to be ready)
2、 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些I/O Model的区别就是在两个阶段上各有不同的情况。
select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点:
1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善
同步与异步、阻塞与非阻塞区别
同步与异步区别在于内核是否会主动通知应用程序相应操作已完成
阻塞与非阻塞的区别在于应用程序发出请求后是否只等待内核该操作的响应而不去干别的事
注:如果是同步非阻塞I/O,应用程序虽不阻塞,但仍需要同过轮询的方式询问内核操作是否完成(对应I/O复用中的select和poll)
参考
1、异步 I/O 模型大体上可以分为两种,反应式( Reactive )模型和前摄式( Proactive )模型
2、浅谈Linux下的五种I/O模型
3、Linux下5种IO模型以及阻塞/非阻塞/同步/异步区别推荐