通俗讲解 异步,非阻塞和 IO 复用

news/2024/7/9 17:44:41 标签: epoll, 运维, 后端

1. 阅前热身

为了更加形象的说明同步异步、阻塞非阻塞,我们以小明去买奶茶为例。

1.1 同步与异步

同步与异步的理解

同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式。 
同步: 当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行。 
异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回。 
异步调用,要想获得结果,一般有两种方式:

  1. 主动轮询异步调用的结果;
  2. 被调用方通过callback来通知调用方调用结果。

生活中的例子

同步买奶茶:小明点单交钱,然后等着拿奶茶;异步买奶茶:小明点单交钱,店员给小明一个小票,等小明奶茶做好了,再来取。

异步买奶茶: 小明要想知道奶茶是否做好了,有两种方式:

  1. 小明主动去问店员,一会就去问一下:“奶茶做好了吗?”…直到奶茶做好。这叫轮训。
  2. 等奶茶做好了,店员喊一声:“小明,奶茶好了!”,然后小明去取奶茶。这叫回调。

1.2 阻塞与非阻塞

阻塞与非阻塞的理解

阻塞与非阻塞的重点在于进/线程等待消息时候的行为,也就是在等待消息的时候,当前进/线程是挂起状态,还是非挂起状态。

阻塞调用在发出去后,在消息返回之前,当前进/线程会被挂起,直到有消息返回,当前进/线程才会被激活.

非阻塞调用在发出去后,不会阻塞当前进/线程,而会立即返回。

生活中的例子

阻塞买奶茶:小明点单交钱,干等着拿奶茶,什么事都不做; 
非阻塞买奶茶:小明点单交钱,等着拿奶茶,等的过程中,时不时刷刷微博、朋友圈。

1.3 总结

通过上面的分析,我们可以得知:

  1. 同步与异步,重点在于消息通知的方式;
  2. 阻塞与非阻塞,重点在于等消息时候的行为。

所以,就有了下面4种组合方式:

  1. 同步阻塞:小明在柜台干等着拿奶茶;
  2. 同步非阻塞:小明在柜台边刷微博边等着拿奶茶;
  3. 异步阻塞:小明拿着小票啥都不干,一直等着店员通知他拿奶茶;
  4. 异步非阻塞:小明拿着小票,刷着微博,等着店员通知他拿奶茶。

2. IO 复用

IO 复用例子说明

假设你是一个机场的空管,你需要管理到你机场的所有的航线, 包括进港,出港,有些航班需要放到停机坪等待,有些航班需要去登机口接乘客。

你会怎么做?

最简单的做法,就是你去招一大批空管员,然后每人盯一架飞机, 从进港,接客,排位,出港,航线监控,直至交接给下一个空港,全程监控。

那么问题就来了:

很快你就发现空管塔里面聚集起来一大票的空管员,交通稍微繁忙一点,新的空管员就已经挤不进来了。空管员之间需要协调,屋子里面就1,2个人的时候还好,几十号人以后 ,基本上就成菜市场了。

空管员经常需要更新一些公用的东西,比如起飞显示屏,比如下一个小时后的出港排期,最后你会很惊奇的发现,每个人的时间最后都花在了抢这些资源上。

现实上我们的空管同时管几十架飞机稀松平常的事情:

此处输入图片的描述

他们怎么做的呢?这个东西叫flight progress strip

每一个块代表一个航班,不同的槽代表不同的状态,然后一个空管员可以管理一组这样的块(一组航班),而他的工作,就是在航班信息有新的更新的时候,把对应的块放到不同的槽子里面。

这个东西现在还没有淘汰哦,只是变成电子的了而已。

是不是觉得一下子效率高了很多,一个空管塔里可以调度的航线可以是前一种方法的几倍到几十倍。

如果你把每一个航线当成一个Sock(I/O 流),空管当成你的服务端Sock管理代码的话.

第一种方法就是最传统的多进程并发模型 (每进来一个新的I/O流会分配一个新的进程管理。) 
第二种方法就是I/O多路复用 (单个线程,通过记录跟踪每个I/O流(sock)的状态,来同时管理多个I/O流 。)

其实I/O多路复用这个坑爹翻译可能是这个概念在中文里面如此难理解的原因。所谓的I/O多路复用在英文中其实叫 I/O multiplexing.

重要的事情再说一遍: I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。

是不是听起来好拗口,看个图就懂了:

此处输入图片的描述

在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流,

最初级的I/O复用

所谓的I/O复用,就是多个I/O可以复用一个进程。

采用非阻塞的模式,当一个连接过来时,我们不阻塞住,这样一个进程可以同时处理多个连接了。

比如一个进程接受了10000个连接,这个进程每次从头到尾的问一遍这10000个连接:“有I/O事件没?有的话就交给我处理,没有的话我一会再来问一遍。” 
然后进程就一直从头到尾问这10000个连接,如果这1000个连接都没有I/O事件,就会造成CPU的空转,并且效率也很低,不好不好。

升级版的I/O复用

上面虽然实现了基础版的I/O复用,但是效率太低了。于是伟大的程序猿们日思夜想的去解决这个问题…终于!

我们能不能引入一个代理,这个代理可以同时观察许多I/O流事件呢?

当没有I/O事件的时候,这个进程处于阻塞状态;当有I/O事件的时候,这个代理就去通知进程醒来?

于是,早期的程序猿们发明了两个代理—selectpoll

select、poll代理的原理是这样的:

当连接有I/O流事件产生的时候,就会去唤醒进程去处理。 
但是进程并不知道是哪个连接产生的I/O流事件,于是进程就挨个去问:“请问是你有事要处理吗?”……问了99999遍,哦,原来是第100000个进程有事要处理。那么,前面这99999次就白问了,白白浪费宝贵的CPU时间片了!痛哉,惜哉…

  1. select是第一个实现 (1983 左右在BSD里面实现)
  2. 1997年实现了poll.
  3. select与poll原理是一样的,只不过select只能观察1024个连接,poll可以观察无限个连接。

上面看了,select、poll因为不知道哪个连接有I/O流事件要处理,性能也挺不好的。

那么,如果发明一个代理,每次能够知道哪个连接有了I/O流事件,不就可以避免无意义的空转了吗?

于是,超级无敌、闪闪发光的epoll,于5年以后, 在2002年被大神 Davide Libenzi 发明出来了。

epoll-io多路复用">epoll IO多路复用

epoll代理的原理是这样的:

当连接有I/O流事件产生的时候,epoll就会去告诉进程哪个连接有I/O流事件产生,然后进程就去处理这个进程。如此,多高效!

epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:

epoll 现在是线程安全的。 
epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。

可是epoll 有个致命的缺点,只有linux支持。于是其他的平台实现类型的多路复用,比如BSD上面对应的是kqueue, win下对应的iocp

epoll和selectpoll区别">epoll和select/poll区别

简单说epoll和select/poll最大区别是

  1. epoll内部使用了mmap共享了用户和内核的部分空间,避免了数据的来回拷贝
  2. epoll基于事件驱动,epoll_ctl注册事件并注册callback回调函数,epoll_wait只返回发生的事件避免了像select和poll对事件的整个轮寻操作。

3. Nginx 异步,非阻塞,IO多路复用

Nginx 这样出众,正是他采用了异步,非阻塞,IO多路复用。

Nginx之前是单进程的。看下他的进程。1个master进程,2个work进程。

$ pstree |grep nginx
 |-+= 81666 root nginx: master process nginx
 | |--- 82500 nobody nginx: worker process
 | \--- 82501 nobody nginx: worker process
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

每进来一个request,会有一个worker进程去处理。但不是全程的处理,处理到什么程度呢?处理到可能发生阻塞的地方,比如向上游(后端)服务器转发request,并等待请求返回。那么,这个处理的worker不会这么傻等着,他会在发送完请求后,注册一个事件:“如果upstream返回了,告诉我一声,我再接着干”。于是他就休息去了。这就是异步。此时,如果再有request 进来,他就可以很快再按这种方式处理。这就是非阻塞IO多路复用。而一旦上游服务器返回了,就会触发这个事件,worker才会来接手,这个request才会接着往下走。这就是异步回调

参考文件:

https://segmentfault.com/a/1190000007614502 
https://www.zhihu.com/question/32163005 
https://www.zhihu.com/question/22062795

 
 

转载于:https://www.cnblogs.com/gongchixin/p/7345661.html


http://www.niftyadmin.cn/n/1110805.html

相关文章

C++解析三

类的构造函数类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。下面的实例有助于更好地理解构…

【python】-- 类的创建、__new__、__metaclass___

类的创建 前面的随笔都是关于类的知识,通过类创建对象,那这个类到底是怎么产生的呢? 1、 传统创建类 class Foo(object):def __init__(self,name):self.name namef Foo("shuaigaogao")f 是通过 Foo 类实例化的对象,其…

linux下安装oracle 10g 的艰难之旅(转)

linux下安装oracle 10g 的艰难之旅(转)[more]  在linux下安装oracle是一件令人生畏的事情,其复杂程度远远超过安装linux操作系统本身。如果能够进行成功的安装oracle,那么同时也就顺便掌握了linux一些技术。本文介绍在redhat linux 下安装oracle 10g 的方法。在这…

Html+CSS常用要点及易错点归纳(一)

最近回顾html和css相关知识的时候,发现很多零碎的知识有些遗忘,所以这里总结归纳一下,既方便我自己的记忆,大家也能一起回顾一下,看看你都记全了吗。 今天先来说说html的相关知识点: Html 相关点&#xff…

Servlet3.0学习总结(三)——基于Servlet3.0的文件上传

原文链接:Servlet3.0学习总结(三)——基于Servlet3.0的文件上传 - 孤傲苍狼 - 博客园https://www.cnblogs.com/xdp-gacl/p/4224960.html 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的com…

fc全连接层的作用、卷积层的作用、pooling层、激活函数的作用

fc:1.起到分类器的作用。对前层的特征进行一个加权和,(卷积层是将数据输入映射到隐层特征空间)将特征空间通过线性变换映射到样本标记空间(也就是label) 2.1*1卷积等价于fc;跟原feature map一样…

在RedHat Linux下安装惠普磁带机(转)

在RedHat Linux下安装惠普磁带机(转)[more]  如果DAT或DLT的磁带机已经连接好并加电,那么Redhat Linux(版本 高于6.2)一般下情况下能够自动检测到磁带机并在自检时(Redhat的 自检)会提示是否要配置发现的磁带机&…

常用的CSS命名规则

一.注释的写法: /* Footer / 内容区 / End Footer */ 二.id的命名: 页面结构 容器: container 页头:header 内容:content/container 页面主体:main 页尾:footer 导航:nav 侧栏:sidebar 栏目:co…