阻塞、非阻塞、同步异步IO模型的理解

之前一直不怎么理解阻塞、非阻塞,同步异步,看字面阻塞就是不动了呗,同步就是按顺序呗,异步就是干别的事去了,好像没什么联系,但是又有那么点联系。看网上的例子看的时候挺明白,但是看完依旧云里雾里,从知乎上搬过来的一个例子。大家图个乐吧

张爱喝茶,废话不说,煮开水。 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

1 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻

2 老张把水壶放到火上,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。

3 老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大

4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 老张觉得自己聪明了。

从专业角度再解释下,一个IO总是分为两个部分(盗一张图),具体可参考这一篇十分推荐,写的太好了https://mp.weixin.qq.com/s/JHqVY02mMJIpuZ4s9XOrVg

  1. 等待数据准备就绪

  2. 将数据从内核缓冲区拷到用户缓冲区

第一步就是等待从网卡到内核缓冲区,这里是需要服务端发送数据,然后网卡才有数据,所以没有数据发送过来就会一直卡在那里,这一步就是阻塞的,这也是传统的IO模型。怎么理解非阻塞,这个时候我们将read函数改造一下,无论是否有数据,我先返回一个东西来,假如没有数据,我就一直返回-1,然后一直循环的去读取,然后第一步不就变成了非阻塞,因为程序不会卡在那了。直到数据准备好了,这时到了第二阶段,数据从内核空间向用户空间拷贝时,非阻塞IO用户进程同样是阻塞的。

异步IO实际上是用户进程发起read操作之后,就会立刻收到一个返回,所以用户进程就可以去完成其他的工作,而不需要阻塞;直到数据准备就绪并且完成了从内核空间向用户空间拷贝的工作,这时用户进程会收到一个通知,告诉他read操作已完成,整个过程中用户进程不会被阻塞

同步,异步,是指两个线程之间的关系,如果线程A对线程B发起请求,A要一直到等B的结果返回了才能继续往下运行,A和B就是同步关系。

如果线程A对线程B发起请求之后,不在原地等结果,直接干别的事情去了,等B有结果了再通知A,A和B的关系就是异步关系。

阻塞,非阻塞,是指单个线程的状态,如果阻塞程序或者你的服务就必须等在这个地方了,无法干别得,如果客户端一直不发数据,别的地方也无法连接到服务器上。

针对这个问题可以,可以用多线程的方式,来了一个用户我就开一个线程,如果线程阻塞了,通过线程的切换就可以实现别的用户的连接。但是此时这一步仍然是阻塞的。

怎么改成非阻塞呢,仍然是关于read函数,如果没有数据到达时(到达网卡并拷贝到了内核缓冲区),立刻返回一个错误值(-1),而不是阻塞地等待。这样,就需要用户线程循环调用 read,直到返回值不为 -1,再开始处理业务。但是这里的第二阶段仍然是阻塞的,就是数据从内核到用户的复制,但是这里阻塞不阻塞影响不大,因为这个系统调用时间是可控的,只需要等待数据从内核缓冲区拷贝到用户缓冲区就可以返回。图如下

阻塞、非阻塞、同步异步IO模型的理解_第1张图片

看到这有人可能会觉得,非阻塞和阻塞也没啥用,真正干活的是异步线程呀,我开了异步,你堵在那关我什么事,反正我在做别的操作了,你好了我再去用你呗。接着往下看吧。

按照上面的方法,为了避免阻塞,开启多个线程,如果每个连接都开启一个线程,十分消耗资源呀,如果我们可以将tcp连接产生的文件描述符放在一个数组里呢,然后用一个函数去遍历这个数组,这里已经有多路复用的感觉了,但是每次read一次都是一次系统调用,每次都是需要从用户态转到内核态呀,开销很大。

用的感觉了,但是每次read一次都是一次系统调用,每次都是需要从用户态转到内核态呀,开销很大。

如果能通过一次操作,把文件描述符传给内核,内核提供一个函数解决下就好了,操作系统提供了select函数,可以返回哪些文件描述符是可读的,后面还有poll和epoll函数用来优化,具体可看我上边推荐的文章。

你可能感兴趣的:(Java学习)