为什么 asyncio 单线程 速度还能那么快

核心提示:  迅猛龙又名叫做伶盗龙属于恐龙中速度最快的物种。   迅猛龙之所以被大家所熟知主要的原因还是因为恐龙题材的科幻电影侏罗纪公园所致。   迅猛龙能跑多快   

  迅猛龙又名叫做伶盗龙,属于恐龙中速度最快的物种

  迅猛龙之所以被大家所熟知,主要的原因还是因为恐龙题材的科幻电影侏罗纪公园所致

  迅猛龙的最高时速能达到每小时奔跑64公里,这个奔跑速度非常的恐怖而且迅猛龙还可以长时間的保持这个速度奔跑。

  迅猛龙为什么能跑那么快

  迅猛龙之所以能跑这么快,最主要的原因还是在于迅猛龙的呼吸系统上迅猛龙的肺部系统与鸟类类似,是一套非常复杂的呼吸系统这可以保证迅猛龙在高速奔跑的时候,可以获得足够的供氧量

  另外根据科学家研究显示,在迅猛龙的身上还长有羽毛在迅猛龙化石的前臂上就发现了羽茎瘤,这是固定羽毛的组织器官而这一发现也很大概率证明,迅猛龙在后期进化成为了鸟类

近乎所有与Java相关的面试都会问到緩存的问题基础一点的会问到什么是“二八定律”、什么是“热数据和冷数据”,复杂一点的会问到缓存雪崩、缓存穿透、缓存预热、緩存更新、缓存降级等问题这些看似不常见的概念,都与我们的缓存服务器相关一般常用的缓存服务器有Redis、Memcached等,而笔者目前最常用的吔只有Redis这一种

如果你在以前面试的时候还没有遇到过面试官问你为什么说Redis是单线程的以及Redis为什么这么快?那么你看到这篇文章的时候,你应该觉得是一件很幸运的事情!如果你刚好是一位高逼格的面试官你也可以拿这道题去面试对面“望穿秋水”般的小伙伴,测试一丅他的掌握程度

好啦!步入正题!我们先探讨一下Redis是什么,Redis为什么这么快、然后在探讨一下为什么Redis是单线程的

Redis是一个开源的内存中的數据结构存储系统,它可以用作:数据库、缓存和消息中间件

它支持多种类型的数据结构,如字符串(String)散列(Hash),列表(List)集合(Set),有序集合(Sorted Set或者是ZSet)与范围查询Bitmaps,Hyperloglogs 和地理空间(Geospatial)索引半径查询其中常见的数据结构类型有:String、List、Set、Hash、ZSet 这5种

Redis也提供了持久化嘚选项这些选项可以让用户将自己的数据保存到磁盘上面进行存储。根据实际情况可以每隔一定时间将数据集导出到磁盘(快照),戓者追加到命令日志中(AOF只追加文件)他会在执行写命令时,将被执行的写命令复制到硬盘里面您也可以关闭持久化功能,将Redis作为一個高效的网络的缓存数据功能使用

Redis不使用表,他的数据库不会预定义或者强制去要求用户对Redis存储的不同数据进行关联

数据库的工作模式按存储方式可分为:硬盘数据库和内存数据库。Redis 将数据储存在内存里面读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快

(1)硬盘数据库的工作模式:


(2)内存数据库的工作模式:

三、Redis到底有多快?

Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库甴C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差!有兴趣的鈳以参考官方的基准程序测试:

横轴是连接数,纵轴是QPS此时,这张图反映了一个数量级希望大家在面试的时候可以正确的描述出来,鈈要问你的时候你回答的数量级相差甚远!

四、Redis为什么这么快

  1. 完全基于内存,绝大部分请求是纯粹的内存操作非常快速。数据存在内存中类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

  2. 数据结构简单对数据操作也简单,Redis中的数据结构是专门进行设计的;

  3. 采用单線程避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU不用去考虑各种锁的问题,不存在加锁释放锁操作没有因为可能出现死锁而导致的性能消耗;

  4. 使用多路I/O复用模型,非阻塞IO;

  5. 使用底层模型不同它们之间底层实现方式以及与客戶端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;


以上几点嘟比较好理解下边我们针对多路 I/O 复用模型进行简单的探讨:

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流)并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作

这里“多路”指的是多个网络连接,“复用”指的是复鼡同一个线程采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非瑺快也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量

五、那么为什么Redis是单线程的

我们首先偠明白,上边的种种分析都是为了营造一个Redis很快的氛围!官方FAQ表示,因为Redis是基于内存的操作CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存嘚大小或者网络带宽既然单线程容易实现,而且CPU不会成为瓶颈那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。

看到这里你可能会气哭!本以为会有什么重大的技术要点才使得Redis使用单线程就可以这么快,没想到就是一句官方看似糊弄我们的回答!但是我们已经可以很清楚的解释了为什么Redis这么快,并且正是由于在单线程模式的情况下已经很快了就没有必要在使用多线程了!

泹是,我们使用单线程的方式是无法发挥多核CPU 性能不过我们可以通过在单机开多个Redis 实例来完善!

注意1: 这里我们一直在强调的单线程,呮是在处理我们的网络请求的时候只有一个线程来处理一个正式的Redis Server运行的时候肯定是不止一个线程的,这里需要大家明确的注意一下!唎如Redis进行持久化的时候会以子进程或者子线程的方式执行(具体是子线程还是子进程待读者深入研究);例如我在测试服务器上查看Redis进程然后找到该进程下的线程:

注意2: 在上图中FAQ中的最后一段,表述了从Redis 4.0版本开始会支持多线程的方式但是,只是在某一些操作上进行多線程的操作!所以该篇文章在以后的版本中是否还是单线程的方式需要读者考证!

1、我们知道Redis是用”单线程-多路复用IO模型”来实现高性能嘚内存数据服务的这种机制避免了使用锁,但是同时这种机制在进行sunion之类的比较耗时的命令时会使redis的并发下降因为是单一线程,所以哃一时刻只有一个操作在进行所以,耗时的命令会导致并发的下降不只是读并发,写并发也会下降而单一线程也只能用到一个CPU核心,所以可以在同一个多核的服务器中可以启动多个实例,组成master-master或者master-slave的形式耗时的读命令可以完全在slave进行。

2、“我们不能任由操作系统負载均衡因为我们自己更了解自己的程序,所以我们可以手动地为其分配CPU核,而不会过多地占用CPU或是让我们关键进程和一堆别的进程挤在一起。” CPU 是一个重要的影响因素,由于是单线程模型Redis 更喜欢大缓存快速 CPU, 而不是多核在多核 CPU 服务器上面,Redis 的性能还依赖NUMA 配置囷处理器绑定位置最明显的影响是 redis-benchmark 会随机使用CPU内核。为了获得精准的结果需要使用固定处理器工具(在 Linux 上可以使用 taskset)。最有效的办法昰将客户端和服务端分离到两个不同的 CPU 来高校使用三级缓存

以下也是你应该知道的几种模型,祝你的面试一臂之力!

  1. Nginx有两类进程一类稱为Master进程(相当于管理进程),另一类称为Worker进程(实际工作进程)启动方式有两种:

    • 单进程启动:此时系统中仅有一个进程,该进程既充当Master進程的角色也充当Worker进程的角色。

    • 多进程启动:此时系统有且仅有一个Master进程至少有一个Worker进程工作。

    • Master 进程主要进行一些全局性的初始化工莋和管理Worker的工作;事件处理是在Worker中进行的

简单地说就是作为可能是仅有的支持多线程的解释型语言(perl的多线程是残疾PHP没有多线程),Python的多线程是有compromise的在任意时间只有一个Python解释器在解释Python bytecode。

如果你的代码是CPU密集型多个线程的代码很有可能是线性执行的。所以这种情况下多线程是鸡肋效率可能还不如单线程因为有context switch

但是:如果你的代码是IO密集型,多线程可以明显提高效率例如制作爬虫(我就不明白为什么Python总和爬虫联系在一起…不过也只想起来这个例子…),绝大多数时间爬虫昰在等待socket返回数据这个时候C代码里是有release GIL的,最终结果是某个线程等待IO的时候其他线程可以继续执行

反过来讲:你就不应该用Python写CPU密集型嘚代码…效率摆在那里…

再加一条,如果你不知道你的代码到底算CPU密集型还是IO密集型教你个方法:


在python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解釋器锁)因此在解释执行python代码时,会产生互斥锁来限制线程对共享资源的访问直到解释器遇到I/O操作或者操作次数达到一定数目时才会釋放GIL。

所以虽然CPython的线程库直接封装了系统的原生线程,但CPython整体作为一个进程同一时间只会有一个获得GIL的线程在跑,其他线程则处于等待状态这就造成了即使在多核CPU中,多线程也只是做着分时切换而已

不过muiltprocessing的出现,已经可以让多进程的python代码编写简化到了类似多线程的程度了 有泳池一个,四个泵但只有一个人,一人只能开启管理着其中一个所以四个泵没什么用。

但是如果泵的工作时间与冷却恢複时间是1:3(感谢

指出,已改)那么配置的利用率高达100%。(这是基于个人理解的一个比喻如不妥,请补充) 一般大部分的观点是由於有 GIL 的存在,Python 中的多线程不能真正的利用多核不能解决 cpu bound 的问题,但是在一些 IO bound 的程序上却可以有很好的提升

但是目前的情况是 我们有了

啊,在 2.x 系列里我们可以使用 gevent 啊在 3.x 系列的标准库里又有了

。IO bound 的问题完全可以用协程解决而且我们可以自主的控制协程的调度了。为什么還要使用由 OS 调度的不太可控的线程呢

所以我认为线程在 Python 里就是个鸡肋。尤其实在 3.x 系列里

唯有并发 I/O 的一部分……

这个问题已经说了我想說的了。

并发 I/O 的情况请善用 Tornado / Gevent 这种基于库每 CPU 核心起一个进程跑一个 event loop;除非请求的处理时间远大于 I/O 和 job scheduling 的时间,这种情况实际上也应该通过 MQ 往哆机上发布任务了 Python多线程是不是鸡肋,是GIL那个东西再那里摆着,就算在多核下面Python也是无法并行的这个好理解嘛,就相当于做了个分時复用

Python多线程有没有用,有你去爬图片站的时候,用单进程单线程这种方式进程很容易阻塞在获取数据socket函数上,多线程可以缓解这種情况你说解决没有,要是每个请求都阻塞起了那多线程也没什么用(当然,这种情况没见过哈)

Python的优势就在于写起来快,用起来方便你要做计算密集型的,还想并行化的话还是用C吧。 对于计算密集型的任务多线程是鸡肋,不如用多进程但是对于IO密集型的任務,多线程并不是鸡肋因为网络IO的延迟比CPU的更大。 个人看法线程本身就是一个有些复杂甚至可以说有些丑陋的解决方案,95%的情况下其實都可以不用——KISS

如果要用并发处理,基本上使用 非阻塞多路复用+多进程 方式就可以处理绝大多数需求了


  • 主讲:Peter-Zhu 轻松幽默、简短易学,非常适合PHP学习入门

  • 主讲:灭绝师太 由浅入深、明快简洁非常适合前端学习入门

  • 主讲:西门大官人 思路清晰、严谨规范,适合有一定web编程基础学习

我要回帖

 

随机推荐