java IO之BIO NIO AIO

 

 

同步异步,阻塞和非阻塞

IO中同步与异步,阻塞与非阻塞区别

同步和异步关注的是消息通信机制 (synchronous communication/asynchronous communication)

同步请求,A调用B,B的处理是同步的,在处理完之前他不会通知A,只有处理完之后才会明确的通知A。

异步请求,A调用B,B的处理是异步的,B在接到请求后先告诉A我已经接到请求了,然后异步去处理,处理完之后通过回调等方式再通知A。

同步和异步最大的区别就是被调用方的执行方式和返回时机。同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回,然后再做事情,做完之后再想办法通知调用方(通过状态、通知来通知调用者,或通过回调函数处理这个调用)。

 

阻塞请求,A调用B,A一直等着B的返回,别的事情什么也不干。

非阻塞请求,A调用B,A不用一直等着B的返回,先去忙别的事情了。

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。

非阻塞:不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

同步是个过程,阻塞是线程的一种状态。

同步、异步说的是被调用者会不会通知,阻塞、非阻塞说的是调用者会不会卡住不动

 

Unix下IO模型

Unix 下共有五种 I/O 模型:

阻塞 I/O
非阻塞 I/O
I/O 多路复用
信号驱动 I/O(SIGIO)
异步 I/O(Posix.1的aio_系列函数)

 

IO模型详解

javaguid哥的io详解,对于内核和用户态  BIO NIO AIO都有详细介绍 可以和下面的文章补充来看

 

你管这破玩意叫 IO 多路复用?

ps:这个人的博客力求讲的过东西通俗易懂,很喜欢他的风格。通过他的博客,又发现了其他牛逼的博文,搞明白了AQS和可重入锁

通过伪代码和动图,生动形象,而且是深入底层原理,强烈推荐看原文来理解

阻塞 I/O

从服务端和客户端的连接过程分析,阻塞主要发生在read过程:从网卡——>内核缓冲区 ——>用户缓冲区

read到有数据才会返回
非阻塞 I/O

在操作系统层面改造read函数,使其成为非阻塞的函数:

如果没有数据到达时(到达网卡并拷贝到了内核缓冲区),立刻返回一个错误值(-1),而不是阻塞地等待。

操作系统提供了这样的功能,只需要在调用 read 前,将文件描述符设置为非阻塞即可

非阻塞为会断的轮询文件描述符,如果发现read返回值不为-1,就开始从内核缓冲区拷贝到用户缓冲区,此时调用 read 函数仍然是阻塞的,需要等待数据从内核缓冲区拷贝到用户缓冲区,才能返回。
I/O 多路复用

使用一个线程处理多个客户端发起的io请求

操作系统提供的select、poll、epoll可以将一批文件描述符(一批io)通过一次系统调用传给内核,由内核层去遍历,减少了一个个read调用时候的系统开销。

select过程:(建议看图)

1、一个线程不断接受客户端连接,并把 socket 文件描述符放到一个 list 里。
2、另一个线程不再自己遍历,而是调用 select,将这批文件描述符 list 交给操作系统去遍历。
3、当 select 函数返回后,用户依然需要遍历刚刚提交给操作系统的 list。
只不过,操作系统会将准备就绪的文件描述符做上标识,用户层将不会再有无意义的系统调用开销。

poll和select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。
完美方案epoll

epoll 是最终的大 boss,它解决了 select 和 poll 的一些问题。

还记得上面说的 select 的三个细节么?

1. select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
2. select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)
3. select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)
所以 epoll 主要就是针对这三点进行了改进。

1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。
2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。
3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

epoll使用红黑树存储文件描述集合,基于以下考虑:

让epoll 在查找效率、插入效率、内存开销等等多个方面比较均衡,最后发现最适合这个需求的数据结构是红黑树。

一切的开始,都起源于这个 read 函数是操作系统提供的,而且是阻塞的,我们叫它 阻塞 IO
为了破这个局,程序员在用户态通过多线程来防止主线程卡死。
后来操作系统发现这个需求比较大,于是在操作系统层面提供了非阻塞的 read 函数,这样程序员就可以在一个线程内完成多个文件描述符的读取,这就是 非阻塞 IO
但多个文件描述符的读取就需要遍历,当高并发场景越来越多时,用户态遍历的文件描述符也越来越多,相当于在 while 循环里进行了越来越多的系统调用。
后来操作系统又发现这个场景需求量较大,于是又在操作系统层面提供了这样的遍历文件描述符的机制,这就是 IO 多路复用
多路复用有三个函数,最开始是 select,然后又发明了 poll 解决了 select 文件描述符的限制,然后又发明了 epoll 解决 select 的三个不足
所以,IO 模型的演进,其实就是时代的变化,倒逼着操作系统将更多的功能加到自己的内核而已。
如果你建立了这样的思维,很容易发现网上的一些错误。
比如好多文章说,多路复用之所以效率高,是因为用一个线程就可以监控多个文件描述符。
这显然是知其然而不知其所以然,多路复用产生的效果,完全可以由用户态去遍历文件描述符并调用其非阻塞的 read 函数实现。而多路复用快的原因在于,操作系统提供了这样的系统调用,使得原来的 while 循环里多次系统调用,变成了一次系统调用 + 内核层遍历这些文件描述符。


信号驱动 I/O(SIGIO)

异步IO

 

java IO模型

 

 

 

posted @ 2019-04-21 19:45  CS408  阅读(304)  评论(0编辑  收藏  举报