什么是阻塞和非阻塞?什么是同步和异步?什么是BIO、NIO、AIO?

一、什么是阻塞和非阻塞?什么是同步和异步?

1.1、阻塞与非阻塞

阻塞与非阻塞是描述进程在访问某个资源时,数据是否准备就绪的的一种处理方式。当数据没有准备就绪时:

  • 阻塞:线程持续等待资源中数据准备完成,直到返回响应结果。
  • 非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。

从线程的角度考虑 ,线程挂起,不再抢夺CPU, 则称为线程被阻塞。

1.2、同步与异步

同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。

  • 同步与异步是指访问数据的机制,同步一般指主动请求并等待IO操作完成的方式。
  • 异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知。

任务的执行需要相互等待、相互协调为同步;各执行各的,不管其他人,是异步。

同步亲自主动查看,异步等通知!

同步:最开始应该是两个人步伐一致,也就是你迈一步的同时我也迈一步,我迈步的时候你不能干别的事,就像连体婴儿,一个上厕所的时候另一个必须得跟着去。计算机里同步是相对谁来说的?答:用户空间(线程)IO操作和内核空间(线程)IO操作

啥是用户空间和内核空间?
答:空间本质上就是内存,用户空间是指用户进程所占用的内存,内核空间指的是系统进程所占用的空间,你玩的windows开机的时候就已经划分好了,这块内存归你,那块内存归系统,你不能在系统占用的内存上操作。

同步体现在哪?
答:同步就是说你内核线程IO读写的时候,我用户线程得等着你完成IO,干不了旁的事儿,这里的重点就在于必须等待对方把事情做完我才能做别的事。

 异步:内核线程在IO操作的时候,用户线程不跟着,而是我想干啥就干啥,等你IO完成之后你可以通知我你IO完事了,得到通知之后我再去选择对这些数据做操作。

同步就是你烧一壶水,你要么一直在旁边守着等着水烧开(同步阻塞),要么一会儿来一趟看水烧开没(同步非阻塞)。异步就是水烧开了会有人通知你,不用你亲力亲为的去监督这件事。

阻塞:阻塞就是把线程堵住了,线程不能去干别的事。阻塞情况下用户线程读取内核空间数据,如果此时还没有数据就会被堵住,一直到有数据才返回。

阻塞与IO有啥关系?
答:当内核空间没有发生IO读写之前,用户线程就等待操作内核空间IO好的数据。

阻塞与同步是一回事儿吗?
答:显然不是,同步针对的是IO操作,阻塞针对的是线程对象。即便内核空间没有IO操作,用户线程同样会发生阻塞。

啥是非阻塞?
答:非阻塞就是线程没被堵住,想干啥干啥。对非阻塞情况,用户线程读取内核空间数据,不管此时有没有数据,用户线程都直接返回。

老王烧开水:
1、普通水壶煮水,站在旁边,主动的看水开了没有?同步阻塞
2、普通水壶煮水,去干点别的事,每过一段时间去看看水开了没有,水没开就继续干别的。 同步非阻塞
3、响水壶煮水,站在旁边,不会每过一段时间主动看水开了没有。如果水开了,水壶自动通知他。 异步阻塞
4、响水壶煮水,去干点别的事,如果水开了,水壶自动通知他。异步非阻塞

归纳

A:阻塞与非阻塞是针对线程来说的,阻塞可能发生在IO期间也可能发生在IO之前。
B:同步与异步是针对IO操作来说的,同步是用户线程一直盯着IO直到完成,异步是用户线程在IO完成时会收到通知。

二、组合概念

2.1 同步阻塞IO(BIO、即传统的IO模型)

同步体现在IO完成之前用户线程不能做别的事情。
阻塞体现在用户线程从发送read请求开始一直到内核线程完成IO读写和数据拷贝都是堵住的。

用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。 

2.2 同步非阻塞IO(NIO)

同步体现在IO完成之前用户线程不能做别的事情。
非阻塞体现在用户线程发送read请求之后没有被堵住而是立刻返回。
这里体现了同步与阻塞的区别,即虽然线程返回了,但是线程在没拿到结果之前干不了别的事情。

用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求(会消耗大量的CPU的资源)

2.3 异步阻塞IO(IO多路复用)

模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

然而,使用select函数的优点并不仅限于此。虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

2.4 异步非阻塞IO(AIO,Asynchronous IO

也叫异步IO,“真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

异步IO模型中,用户线程直接使用内核提供的异步IO API发起read请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核,然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor,Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数),完成异步IO。

三、NIO模型

NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。

传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。

在标准的 IO API 中,你可以使用字节流和字符流。在 NIO 中,你使用的是ChannelBuffer。数据总是从一个Channel读到一个Buffer,或者从一个Buffer写到一个Channel。NIO 包含“selectors”的概念。一个Selector是一个对象,它可以监控多个Channel的事件(如:连接打开,数据到达等)。因此,一个线程可以监控多个Channel的数据。 

IO:

IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 

NIO:

一个线程可以从一个 channel(通道)将数据读到一个Buffer(缓冲区)中。当Channel将数据读入Buffer时,线程可以做其他事情。一旦数据读入Buffer结束,线程就可以继续处理数据。向Channel写入数据也是如此。

NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

    NIO优点:

  1. 通过Channel注册到Selector上的状态来实现一种客户端与服务端的通信。
  2. Channel中数据的读取是通过Buffer , 一种非阻塞的读取方式。
  3. Selector 多路复用器 单线程模型, 线程的资源开销相对比较小。

一个Selector允许一个线程处理多个Channel。如果您的应用程序有许多连接(Channel)打开,但每个连接的流量很小,这就很方便。例如聊天服务器。

补充一:Redis是单线程为啥还快?

Redis采用了IO多路复用(异步阻塞),可达到在同一个线程内同时处理多个IO请求的目的。同样的效果,在同步阻塞模型中,必须通过多线程的方式才能达到。

可以打个比方:

就好比去银行办业务,只有一个窗口开放,大家都排在这个窗口下等待办业务。轮到某个人时,他的业务需要先要向银行经理阐述业务需求然后从窗口领到表格(2分钟),然后到一边填写表格(10分钟)。

多路复用就是:你去填表格时,银行经理不停下,继续接待下一个客户,等你这边填完了表格继续回去窗口那里完成业务办理。

好处就是银行经理一直在办业务,没有停下来等待,业务处理效率就能得到保证。这里一个窗口就好比是单线程,这里银行经理就是一个线程,每个客户就是一个Socket,每个业务就是一个IO请求,一个线程(客户经理)能够不间断地处理多个Socket(客户)的多个业务(IO)。

同步阻塞 或多线程 就是:多开几个服务窗口(多个线程)。

Redis即使是单线程的,但是它的操作是异步的,每个读、写操作都是不需要等待的,再加上都是基于内存的操作因此很快。

猜你喜欢

转载自blog.csdn.net/weixin_41231928/article/details/107604398