recvfrom函数 非阻塞_那些年让你迷惑的阻塞、非阻塞、异步、同步

  • Post author:
  • Post category:其他





那些年让你迷惑的阻塞、非阻塞、异步、同步



在IT圈混饭吃,不管你用什么编程语言、从事前端还是后端,阻塞、非阻塞、异步、同步这些概念,都需要清晰地掌握,否则,怎么与面试官谈笑风生(chui niu pi)?但是,掌握这些概念又不是非常容易,尤其对非科班出身的,更加困难。本文试图给出一个清晰简明但不失深刻的介绍,希望对大家有所帮助。




1、从I/O说起



这些概念之所以容易令人迷惑,在于很多人对I/O就没有清晰准确的理解,后面的理解自然不可能正确。我想用一个具体的例子来说明一下I/O。 设想自己是一个进程,就叫小进吧。小进需要接收一个输入,我们不管这个输入是从网络套接字来,还是键盘,鼠标来,输入的来源可以千千万万。但是,都必须由内核来帮小进完成,为啥内核这么霸道?因为计算机上运行的可不只是咱小进一个进程,还有很多进程。这些进程兄弟也可能需要从这些输入设备接收输入,没有内核居中协调,岂不是乱套。 从小进的角度看,内核帮助它完成输入,其实包括三个步骤:

  • 1、内核替小进接收好数据,这些数据暂时存在内核的内存空间
  • 2、内核将数据从自己的内存空间复制到小进的内存空间
  • 3、告诉小进,输入数据来了,赶快读吧

这三步看似挺简单,其实在具体实现时,有很多地方需要考虑:

  • 0、小进如何告诉内核自己要接收一个输入?
  • 1、内核接到小进的请求,替小进接收好数据这段时间, 小进咋办?
  • 2、内核在将数据复制到小进的内存空间这段时间,小进咋办?
  • 3、到底什么时候告诉小进数据准备好了,是在内核接收好数据之后就告诉小进,还是在将数据复制到小进的内存空间之后再告诉他?
  • 4、内核以什么样的方式告诉小进,数据准备好了?




2、阻塞式I/O模型



对上面5个问题,最简单的解决方案就是阻塞式I/O模型,它的过程是这样的: 小进:内核内核,我要接收一个键盘输入,快点帮我完成! 内核:好咧!biubiu!内核迅速将小进阻塞,可怜的小进顿时石化,就像被孙悟空点了定一样。 就这样,小进在石化中,时间一点点流逝。终于,内核收到了数据。 内核:数据终于来了,我要开干了!duang duang duang,先把数据存在自己的内核空间,然后又复制到小进的用户空间。 内核:biubiu!内核解除了小进的阻塞,小进瞬间复活,小进的记忆还是停留在让内核帮他接收输入时。 小进:哇!内核真靠谱,数据已经有了!干活去!

我们可以看到,小进发出接收输入的请求给内核开始,就处于阻塞状态,直到内核将数据复制到小进的用户空间,小进才解除阻塞。 以上过程可通过下图来解释:
00e234a95bf672ed6b28dc022baa136e.png




3、非阻塞式I/O



小进发现,阻塞式I/O中,自己总要被阻塞好久,好不爽啊,于是小进改用了非阻塞式I/O,其过程是这样的: 小进:内核内核,我要接收一个输入,赶紧帮我看看,数据到了没有,先说好,不要阻塞我。 内核:查看了一下自己的内核空间,没有发现数据,于是迅速告诉小进,没有呢!并继续帮小进等着数据。 如此这样,小进不断地问内核,终于,过了一段时间,小进再一次询问时,内核往自己的空间中一查,呦!数据来了,不胜其烦的内核迅速告诉小进,数据好了! 小进:快给我! 内核:biu!内核干净利落地阻塞了小进,悲催的小进还是石化了! 内核赶紧将自己空间的输入数据复制到小进的用户空间,复制好后。 内核:biu!内核解除了小进的阻塞,小进立马复活 小进:哇!数据来了,啥也不说,干活!

我们看到,所谓的非阻塞I/O,其实在内核将数据从内核空间复制到小进的用户空间时,小进还是被阻塞的。 具体过程如下图所示:
a2b8c6c1fe13fe04cf711bf620375b0b.png




4、异步I/O



上面的两种I/O解决方案中,小进都被阻塞了,只不过是阻塞时间长短不一样,第一种方案中小进被阻塞的时间长一些,在内核接收数据以及将数据复制到小进的用户空间时,都被阻塞。 第二种方案中,只在内核将数据从内核空间复制到小进的用户空间时,小进才被阻塞。 我们现在说的异步I/O,目的就是让小进绝对不被阻塞。其过程是这样的: 小进:内核内核,我要接收一个输入,弄好了告诉我。同时将一个信号和信号处理函数告诉内核,然后继续干自己的活了。 内核:得了您嘞,您先忙。 一直到内核接收到数据并将数据从内核空间复制到小进的用户空间后,内核才给小进发送信号。小进在信号处理函数中可以直接处理数据。 其过程可用下图解释:
e72c551eddeb6b603a7f4fdf1a87339b.png




5、IO多路复用



在上面的阻塞IO中,我们的小进都是直接阻塞在IO系统调用recvfrom上,可以说是与IO系统调用“零距离”。

这种做法有一定的弊端,就是当小进要同时等待多个输入,哪个先来就处理哪个时,小进不知道哪个先来,自然也就不知道应该被阻塞到哪一个输入的recvfrom上好。

聪明如你,可能会说,那全部用异步IO呗。但这样也不方便,就是小进要为每一个输入都在系统中注册一个信号和信号处理函数,这可是要与内核打交道的。如果要等的输入非常多,岂不是非常的麻烦。

为了方便处理这种情况,小进可以使用一个输入管家,就是select系统调用,该调用可以帮助小进管理所有的输入(通常是套接字),每当一个输入中有数据到来时,就告诉小进。这样小进实际上是阻塞在select系统调用上,而不是阻塞在真正的IO系统调用上,如下图所示:
36eb5a226d294ef5fcf3420ff9c3856d.png




6、那啥是同步呢?



一句话,凡是让小进阻塞(不管长短)的I/O方案都是同步I/O。也就是说,阻塞、非阻塞、信号驱动式都是同步I/O。




7、无总结,不进步



上面,我们从完成输入时,进程与内核的交互方式的角度分析了不同的I/O解决方案,在这个过程中,解释清楚了阻塞、非阻塞、同步、异步的概念。



版权声明:本文为weixin_39678089原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。