今天给大家分享两张图。它们是如此重要,以至于你看到的许多软件设计都与它们有关。可以说图片中出现的问题都是电脑的本质问题。图1计算机各部件的速度可以看出,CPU最快,一个时钟周期为0.3纳秒,内存访问需要120纳秒,固态硬盘访问需要50-150微秒,传统硬盘访问需要1-10毫秒,网络访问最慢几十毫秒。这张图最有意思的地方在于,它把计算机世界的时间和人类世界的时间进行了对比。我经常把CPU比作跑得快但记不住东西的“阿甘正传”。他的一个时钟周期如果按1秒来算:内存访问是6分钟,固态硬盘是2-6天,传统硬盘是1-12个月,网络访问是几年!(1秒=1000毫秒=1000,000微秒=1000,000,000纳秒)如果你是CPU,你会觉得这个世界真的慢的要死!从硬盘读取数据要等“几天”甚至“几个月”!图2内存层级结构来源:《深入理解计算机系统》第三版图2图1中的信息已经层级化,增加了价格信息,说明了一个道理:天下没有免费的午餐。内存速度越高越快,但价格越来越贵,越低越慢,但价格越来越便宜。这两张图有什么意义?正是由于计算机各部件的速度、容量和价格的不同,导致了计算机系统/编程中的各种问题和相应的解决方案。让我举几个例子。Case1CPU的速度超快,总不能一直闲着吧,得把它榨干!这里有两个强有力的理由:1.人类需要多个程序“同时”运行,我们需要对CPU的时间进行切片,让每个程序在CPU上轮换,造成多个程序同时运行的错觉,即,并发。2、当CPU遇到IO操作(硬盘、网络)时,不能坐以待毙“几个月”甚至“几年”,必须转而执行其他程序。说起来简单,但是程序的切换需要保存程序的执行位置,以便后面恢复执行,所以需要一个数据结构来表示,就是进程。如果一个进程只有一个“执行流”,如果进程等待硬盘的操作,程序就会阻塞,无法响应用户输入,所以必须有多个“执行流”,即线程。Case2.需要持久化的数据必须保存到硬盘,但是硬盘超级慢,无法支持大量并发访问。我应该怎么办?可以将访问频率最高的热点数据放在CPU的缓存中。其实CPU也是这么干的,只是CPU的L1、L2、L3缓存太小了,根本满足不了需要。所以我不得不退而求其次,将热数据放在较慢的内存中,于是出现了应用程序缓存。缓存虽然解决了问题,但是也带来了更多的问题,比如:如何保持缓存数据和数据库数据的一致性?缓存崩溃了怎么办?数据不能存储在一台机器的内存中,所以需要分布式怎么分发到多台机器,用什么算法?...Case3考虑像Tomcat这样的应用服务器,用一个线程来处理每一个要求。如果现在有10000个请求进来,Tomcat会不会创建10000个线程来处理呢?不行,因为线程太多,开销大,而且线程切换会很慢,所以只好用线程池来复用线程。现在假设线程池中有一千个可用线程(已经很多了),它们都被发送去访问硬盘、数据库,或者发起网络调用,这是一个非常慢的操作,导致这千个线程等待结果返回(阻塞),那么剩下的9000个请求就不能处理了吧?于是后来人们又发明了一种新的处理方式,只用很少的线程(比如和CPU核心数一样多),让他们疯狂运行。当遇到I/O操作时,程序注册一个钩子函数并放置在那里,然后线程处理其他请求。当I/O操作完成后,系统向线程发送事件,线程返回Go过来调用之前的钩子函数(也叫回调函数)进行处理。这是一种异步的、非阻塞的做事方式。Node.js、Vert.x等都采用了类似的思想。案例4Redis使用单线程处理请求,为什么可以使用单线程呢?为什么不像Tomcat那样使用多线程和线程池呢?因为它只面向内存,而内存的速度在计算机系统中是仅次于CPU的,比那些网络操作不知道快在哪里(大家可以回头看看网速有多慢在第一张图中!)所以这个唯一的线程可以快速执行内存读写操作,完成来自许多网络的缓存请求。单线程还有一个巨大的优势,没有竞争,不需要加锁!看,我们软件中的很多问题都是由计算机各个组件的速度差异引起的。这是一个想法。如果硬盘的速度和内存一样快,并且能够持久存储,数据不会像内存一样断电丢失,那我们的电脑和系统会是什么样子呢?【本文为专栏作家“刘欣”原创稿件,转载请通过作者微信获取授权公众号编码】点此查看该作者更多好文
