七叶笔记 » golang编程 » 阻塞&非阻塞&同步&异步之间的关系

阻塞&非阻塞&同步&异步之间的关系

一:阻塞与非阻塞

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

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

阻塞调用是指调用结果返回之前,当前 线程 会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

1.blockingIO(阻塞):

1.1:阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。

1.2:对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
socket 接收数据函数 recv 是一个阻塞调用的例子。

1.3:当socket工作在阻塞模式的时候,如果没有数据的情况下调用该函数,则当前线程就会被 挂起,直到有数据为止。

图一就是recv函数在接受信息时的流程图

当用户进程调用了recv这个系统调用,内核就开始了IO的第一个阶段:准备数据。对于networkio来说,很多时候数据在一开始还没有到达,这个时候内核就要等待数据的到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从网卡 缓存 区中拷贝到系统缓存区然后拷贝到用户指定缓存区中,然后内核返回结果,用户进程才解除阻塞状态,重新运行起来所以,blockingIO的特点就是在IO执行的两个阶段都被阻塞了。

2.nonblockingIO(非阻塞):

Linux 下,可以通过设置socket使其变为non-blocking。

2.1 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

2.2:对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。

2.3;:阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。

从图二是通过设置socket使其变为非阻塞。可以看出,当用户进程发出recv操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个recv操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送recv操作。一旦内核中的数据准备好了,并且又再次收到了用户进程的recv调用那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程其实是需要不断的主动询问内核数据好了没有。

其实这样过程与阻塞本质是一样的只是需要不断的向内核询问,有数据到没有拷贝方式也和阻塞时一样。图三以select为例,我们每次将所有的 文件描述符 放入集合中通过select进行遍历进行查询如果有数据到来 集合就保留该文件描述符。这种方式的运行效率与用户的在线量成反比。

二、同步与异步

同步是两个对象之间的关系,而阻塞是一个对象的状态。

同步和异步关注的是消息通信机制(synchronouscommunication/ asynchronouscommunication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。

而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用

1.synchronousIO(同步):

同步就是一旦调用那么就必须有结果才返回,在没有得到结果之前会一直等待,可能有些同学会问同步非阻塞呢,同步非阻塞是得到了结果的。只是这个结果表示没有数据或者error;

以上的阻塞和非阻塞均是同步状态。

2.asynchronousIO(异步):

如图五所示再用户进程发起recv操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronousrecv之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核会给用户进程发送一个signal,告诉它recv操作完成了。

经过上面的介绍,会发现non-blockingIO和asynchronousIO的区别还是很明显的。在non-blockingIO中,虽然进程大部分时间都不会被阻塞,但是它仍然要求进程去主动的检查,并且当数据准备完成以后,也需要进程主动的再次调用recv来将数据拷贝到用户内存。而asynchronousIO则完全不同。它就像是用户进程将整个IO操作交给了他人(内核)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

最后总结一下:

很多人也会把异步和非阻塞混淆,
因为异步操作一般都不会在真正的IO操作处被阻塞,
比如如果用select函数,当select返回可读时再去read一般都不会被阻塞
就好比当你的号码排到时一般都是在你之前已经没有人了,所以你再去柜台办理业务就不会被阻塞.
可见,同步/异步与阻塞/非阻塞是两组不同的概念,它们可以共存组合,
而很多人之所以把同步和阻塞混淆,我想也是因为没有区分这两个概念,
比如阻塞的read/write操作中,其实是把消息通知和处理消息结合在了一起,
在这里所关注的消息就是fd是否可读/写,而处理消息则是对fd读/写.
当我们将这个fd设置为非阻塞的时候,read/write操作就不会在等待消息通知这里阻塞,
如果fd不可读/写则操作立即返回.

注:需要C/C++ Linux服务器开发学习资料私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

相关文章