松本行弘

小结

这本书泛泛的介绍了编程的一些背景、原理、过去以及未来,同时通俗的介绍了一些基础的编程原理、计算机原理、算法等。预测了未来的一些编程可能,逐个的介绍了go、ruby等语言的一些特点,未来并发编程的一些畅想。

————书籍摘要————

◆ 编程的时间和空间

  • 因此,创造出一种人类和计算机都能够理解的语言(编程语言),并通过这样的语言将人类的意图传达给计算机,这样的行为就叫做编程。

  • 用编程语言将计算机需要执行的操作步骤详细描述出来,就成了软件。

  • 尽管看上去是和计算机打交道的工作,但实际上编程的对象还是人类,因此这是个非常“有人味”的工作。个人认为,编程是需要人来完成的工作,因此我不相信在将来计算机可以自己来编程。

  • 计算机在不断进化,而算法则保持不变。算法就能以不变应万变。

  • 我也希望大家不仅仅是将软件开发作为一份工作来做,而是希望更多的人能够感受到软件开发所带来的那种“创造的乐趣”和“心潮澎湃的感觉”。

  • “巴纳姆效应”是一种心理学现象,指的是将一些原本是放之四海而皆准的、模棱两可的一般性描述往自己身上套,并认为这些描述对自己是准确的。

◆ 编程语言的过去、现在和未来

  • 人们用更加容易记忆的指令(助记符)来代替数值,并开发了一种能够自动生成机器语言的程序,这就是汇编器。

  • 20年后的语言,应该是在分布处理(多台计算机协作处理)和并行处理(多个CPU协作处理)功能上进行强化,使得开发者不需要特别花心思就能够使用这些功能。

◆ DSL(特定领域语言)

  • 首先我们从DSL(Domain Specific Language,特定领域语言)开始说起。所谓DSL,是指利用为特定领域(Domain)所专门设计的词汇和语法,简化程序设计过程,提高生产效率的技术,同时也让非编程领域专家的人直接描述逻辑成为可能。DSL的优点是,可以直接使用其对象领域中的概念,集中描述“想要做到什么”(What)的部分,而不必对“如何做到”(How)进行描述。

  • 应用程序开发,可以理解为是将库等组件设计成针对该应用程序对象领域的DSL,最后再进行整合的过程。这样编写出来的应用程序,其代码的抽象度高,应对未来修改的能力强,一定是一个不错的应用程序。

  • 构成一种优秀的(内部)DSL的要素包括下列5种:
    ❑ 上下文(Context)
    ❑ 语句(Sentence)
    ❑ 单位(Unit)
    ❑ 词汇(Vocabulary)
    ❑ 层次结构(Hierarchy)

  • 在软件开发之前就设计好规格的开发手法,通常不是叫做测试驱动开发,而是叫做行为驱动开发(Behavior Driven Development, BDD)。DSL是一种对领域特定内容的高度抽象,用近乎描述或者叙述what的方式,来实现对领域特定内容的技术方案输出

  • DSL也可能作为判断设计优秀与否的重要指标,对软件开发产生巨大的影响。

◆ 元编程

  • 从这个意思引申出来,在单词前面加上meta,表示对自身的描述。例如,描述数据所具有的结构的数据,也就是关于数据本身的数据,被称为元数据(Metadata)

  • 所谓元编程,就是“用程序来编写程序”的意思。

  • 像这样获取和变更程序本身信息的功能,被称为反射(Reflection)

  • 程序是由数据结构和算法构成的,然而,如果环境允许程序本身作为数据结构来操作的话,那么元编程也就和面向一般数据结构的一般操作没什么两样了。

◆ 内存管理

  • 所谓虚拟内存,就好比是将书桌上比较老的文件先暂时收到抽屉里,用空出来的地方来摊开新的文件。

  • 硬盘的容量比内存大,但相对的,速度却非常缓慢,如果和硬盘之间的数据交换过于频繁,处理速度就会下降,表面上看起来就像卡住了一样,这种现象被称为抖动(Thrushing)。应该有很多人有过计算机停止响应的经历,而造成死机的主要原因之一就是抖动。

  • 将内存管理,尤其是内存空间的释放实现自动化,这就是GC。如果程序(通过某个变量等等)可能会直接或间接地引用一个对象,那么这个对象就被视为“存活”;与之相反,已经引用不到的对象被视为“死亡”。将这些“死亡”对象找出来,然后作为垃圾进行回收,这就是GC的本质。

  • 标记清除(Mark and Sweep)是最早开发出的GC算法(1960年)。它的原理非常简单,首先从根开始将可能被引用的对象用递归的方式进行标记,然后将没有标记到的对象作为垃圾进行回收。在扫描的同时,还需要将存活对象的标记清除掉,以便为下一次GC操作做好准备。

    • 标记清除算法有一个缺点,就是在分配了大量对象,并且其中只有一小部分存活的情况下,所消耗的时间会大大超过必要的值,这是因为在清除阶段还需要对大量死亡对象进行扫描。
  • 标记压缩(Mark and Compact)的算法,它不是将被标记的对象清除,而是将它们不断压缩。

  • 复制收集(Copy and Collection)则试图克服这一缺点。在这种算法中,会将从根开始被引用的对象复制到另外的空间中,然后,再将复制的对象所能够引用的对象用递归的方式不断复制下去。

    • 和标记相比,将对象复制一份所需要的开销则比较大,因此在“存活”对象比例较高的情况下,反而会比较不利。
  • 引用计数它的基本原理是,在每个对象中保存该对象的引用计数,当引用发生增减时对计数进行更新。

    • 由于释放操作是针对每个对象个别执行的,因此和其他算法相比,由GC而产生的中断时间(Pause time)就比较短,这也是一个优点。
    • 引用计数最大的缺点,就是无法释放循环引用的对象
    • 引用计数的第二个缺点,就是必须在引用发生增减时对引用计数做出正确的增减,而如果漏掉了某个增减的话,就会引发很难找到原因的内存错误
    • 最后一个缺点就是,引用计数管理并不适合并行处理。
  • 分代回收、增量回收和并行回收

    • 分代回收的基本思路,是利用了一般性程序所具备的性质,即大部分对象都会在短时间内成为垃圾,而经过一定时间依然存活的对象往往拥有较长的寿命。
    • 在分代回收中,对象按照生成时间进行分代,刚刚生成不久的年轻对象划为新生代(Young generation),而存活了较长时间的对象划为老生代(Old generation)
    • 像这种只扫描新生代对象的回收操作,被称为小回收(Minor GC)
    • 首先从根开始一次常规扫描,找到“存活”对象。这个步骤采用标记清除或者是复制收集算法都可以,不过大多数分代回收的实现都采用了复制收集算法。
    • 然后,将第一次扫描后残留下来的对象划分到老生代。具体来说,如果是用复制收集算法的话,只要将复制目标空间设置为老生代就可以了;而用标记清除算法的话,则大多采用在对象上设置某种标志的方式。
    • 在分代回收中,会对对象的更新进行监视,将从老生代对新生代的引用,记录在一个叫做记录集(remembered set)的表中。在执行小回收的过程中,这个记录集也作为一个根来对待。
    • 像这样以全部区域为对象的GC操作被称为完全回收(Full GC)或者大回收(Major GC)。
    • 分代回收通过减少GC中扫描的对象数量,达到缩短GC带来的平均中断时间的效果。
    • 在对实时性要求很高的程序中,比起缩短GC的平均中断时间,往往更重视缩短GC的最大中断时间
    • 为了维持程序的实时性,不等到GC全部完成,而是将GC操作细分成多个部分逐一执行。这种方式被称为增量回收(Incremental GC)。
    • 由于增量回收的过程是分步渐进式的,可以将中断时间控制在一定长度之内。另一方面,由于中断操作需要消耗一定的时间,GC所消耗的总时间就会相应增加,正所谓有得必有失。
    • 并行回收的基本原理是,是在原有的程序运行的同时进行GC操作,这一点和增量回收是相似的。
    • 为了解决这个问题,并行回收也需要用写屏障来对当前的状态信息保持更新。
  • 像标记清除和复制收集这样,从根开始进行扫描以判断对象生死的算法,被称为跟踪回收(Tracing GC)

  • 相对的,引用计数算法则是当对象之间的引用关系发生变化时,通过对引用计数进行更新来判定对象生死的。

  • 任何一种GC算法,都是跟踪回收和引用计数回收两种思路的组合。

◆ 异常处理

  • 所谓正常化偏见指的是人们的一种心理倾向,对于一些偶然发生的情况,一旦发生了便会不自觉地忽略其危害。
  • 正如墨菲定律所说的,即便是极少会发生的情况,只要有发生的可能性,早晚是会发生的。
    • 第一,由于对错误的检测不是强制进行的,可能会出现没有注意到发生了错误而继续运行程序的情况。
    • 第二,原本的程序容易被错误处理埋没。没有检查错误就继续运行,错误处理将原本的程序埋没
    • 当产生异常时也不能总是让程序结束运行,当显式声明需要进行错误处理时,可以恢复产生的错误,并让程序继续运行。
  • 对于在该方法定义中所声明的异常,如果没有用异常处理来进行捕获,且没有用throws继续抛给上层的话,就会产生一个编译错误,因为异常已经成为方法的数据类型的一部分了。像这样的异常被称为检查型异常(checked exception)。
  • 检查型异常也遭到了一些批判。异常之所以被称为异常,本来就因为它很难事先预料到。明知如此,还非要在代码中强制性地事先对异常做好声明,以避免产生编译错误,这实在是太痛苦了

◆ 闭包

  • 因为函数对象不一定是闭包。不过话说回来,要理解闭包,首先要理解函数对象,所谓函数对象,顾名思义,就是作为对象来使用的函数。
  • 所谓高阶函数,就是用函数作为参数的函数。高阶函数这样的方式,通过将一部分处理以函数对象的形式转移到外部,从而实现了算法的通用化。
  • 作用域(Scope)和生存周期(Extent)。
  • 从函数对象中能够对外部变量进行访问(引用、更新),是闭包的构成要件之一。
  • 在函数对象中,将局部变量这一环境封闭起来的结构被称为闭包。
  • “过程与数据的结合”是形容面向对象中的“对象”时经常使用的表达。对象是在数据中以方法的形式内含了过程,而闭包则是在过程中以环境的形式内含了数据。

◆ 编程语言的新潮流

  • 可移植性,功能强大,高性能,丰富的库。java在服务器端获得成功的四大理由

  • 有一种叫做JIT(Just In Time)编译的技术,可以在运行时将字节码转换成机器语言,经过转换之后就可以获得和原生编译一样快的运行速度。

  • Go具有很多特性,(从我的观点来看)比较重要的有下列几点:
    ❑ 垃圾回收
    ❑ 支持并行处理的Goroutine
    ❑ Structural Subtyping(结构子类型)

  • 所谓静态,就是无需实际运行,仅根据程序代码就能确定结果的意思;而所谓动态,则是只有到了运行时才能确定结果的意思。

  • 对程序自身进行操作的编程,也被称为元编程(Metaprogramming)。

◆ Go

  • 用于基础架构等系统编程的C和C++、兼具高开发效率和高性能的Java、用于客户端编程的JavaScript,再加上高开发效率的动态语言Python,我认为这是一组十分均衡的选择

  • 即大写字母开头的名称表示可以从包外部访问的公有对象,而小写字母开头的名称表示只能从包内部访问的私有对象。由于有了这条规则,Go中几乎所有的方法名都是大写字母开头的。

  • Go中规定:声明必须以保留字开头,且类型位于变量名之后。

  • 由于Go没有继承,因此通常的变量没有多态性,方法调用的连接是静态的

  • 在Go中,是通过defer语句来实现后处理的。defer语句所指定的方法,在该函数执行完毕时一定会被调用。

  • Go则在语言中内置了对并发编程的支持,这一功能参考了CSP(Communicating Sequential Processes,通信顺序进程)模型。

  • Go的goroutine支持内存空间共享,是轻型的,且支持自动上下文切换,因此可以充分利用多核的性能。在实现上,是根据核心数量,自动生成操作系统的线程,并为goroutine的运行进行适当的分配。

◆ Dart

  • 所使用的语言种类越多,就需要雇佣越多精通这些语言的技术人员,而限制开发语言的种类,主要是从降低管理成本上来考虑的

  • 此外,JavaScript目前受到的主要批判,如:
    ❑ 无法应对复杂的互联网应用程序
    ❑ 无法进行高速化
    ❑ 不支持多核/GPU
    ❑ 无法修正

  • 在Dart中,没有指定类型的变量和表达式会被当做Dynamic型,其类型检查在运行时完成

  • 个人认为,Dart的未来还真不能说有多么光明。理由有很多,首先一个就是期望与现实的差距。

  • 此外,Dart当初的目标是为了打倒JavaScript,但它的对手拥有大量的用户、社区和应用程序

◆ CoffeeScript

  • 最近的JavaScript引擎中,由于采用了JIT、特殊化、分代垃圾回收等技术,在动态语言中已经可以归入速度最快的级别了。

  • JIT是Just In Time Compiler的缩写,指的是在程序运行时将其编译为机器语言的技术。

  • 所谓特殊化,指的是一种在将函数转换为内部表达时所运用的技术。通过假定参数为特定类型,事先准备一个特殊化的高速版本,在函数调用的开头先执行类型检查,当前提条件成立时直接运行高速版本。

  • 动态语言运行速度慢的理由之一,就是因为在运行时需要伴随大量的类型检查,而通过特殊化则可以回避这一不利因素

  • 进一步说,CoffeeScript的编译器是用JavaScript编写的。也就是说,只要有JavaScript, CoffeeScript编写的程序就可以在浏览器上直接运行

  • CoffeeScript利用这个优势,无论在服务器端还是客户端,今后其应用范围都会越来越广,可以说是将来值得期待的语言之一。

◆ Lua

  • Lua是由巴西里约热内卢天主教大学的Roberto Ierusalimschy等人开发的一种编程语言。据我所知,诞生于南美洲,并在全世界范围内获得应用的编程语言,Lua应该是第一个。当然,我不知道除了Lua之外还有没有其他语言是来自巴西的。
    说句题外话,编程语言及其作者的国籍多种多样(表1),大家可以看出,并不是所有的编程语言都是诞生于美国的,相反,貌似还是欧洲阵营更加强大一些。尤其是从人口比例来看的话,来自北欧的语言设计者比例相当高,而来自南美的只有Lua,来自亚洲的则只有Ruby,真是太寂寞了。

  • Lua的解释器是完全在ANSI C的范围内编写的,实现了极高的可移植性

◆ 云计算时代的编程

  • 所谓查找,就是在数据中找出满足条件的对象。最简单的数据查找算法是线性查找。所谓线性查找其实并不难,只要逐一取出数据并检查其是否满足条件就可以了,

  • 使用二分法查找的前提条件是,数据之间存在大小关系,且已经按照大小关系排序。利用这一性质,查找的计算量可以下降到O(log n)。

  • 应对冲突的方法大体上可以分为链地址法(chaining)和开放地址法(open addressing)两种。

  • 布隆过滤器是一种可以判断某个数据是否存在的数据结构,或者也可以说是判断集合中是否包含某个成员的数据结构。布隆过滤器的特点如下:
    ❑ 判断时间与数据个数无关(O(1))
    ❑ 空间效率非常好
    ❑ 无法删除元素
    ❑ 偶尔会出错(! )

  • 像布隆过滤器这样“偶尔会出错”的算法,被称为概率算法(probabilistic algorithm)。

  • GFS(Google File System)这样的分布式文件系统技术。GFS是后面要讲到的MapReduce的基础。

  • MapReduce是将数据的处理通过Map(数据的映射)、Reduce(映射后数据的化简)的组合来进行描述的。

◆ C10K问题

  • 所谓C10K问题,就是Client 10000 Problem,即“在同时连接到服务器的客户端数量超过10000个的环境中,即便硬件性能足够,依然无法正常提供服务”这样一个问题。

  • 为了避免这种浪费,从HTTP1.1开始,对同一台服务器产生的多个请求,都通过相同的套接字连接来完成,这就是keep-alive技术。

  • 当大量的进程导致内存开销超过物理内存容量时,每次进行进程切换都不得不产生磁盘访问,这样一来,消耗的时间太长导致操作系统整体陷入一种几乎停止响应的状态,这样的情况被称为抖动(thrashing)。

  • 所谓文件描述符(file descriptor),就是用来表示输入输出对象的整数,例如打开的文件以及网络通信用的套接字等。

◆ HashFold

  • MapReduce通过分解、提取数据流的Map函数和化简、计算数据的Reduce函数,对处理进行分割,从而实现了对大量数据的高效分布式处理。
  • HashFold的功能是以散列表的方式接收Map后的数据,然后通过Fold过程来实现对散列表元素的去重复。
  • 线程和进程在创建的时候就伴随着一定的开销,因此像这样先创建好再重复利用的技术是非常普遍的。这种重复利用的技术被称为池(pooling)。

◆ 进程间通信

  • 资源调度的最小单位就是进程,应用程序都是以进程形式存在和执行的。程序流执行的最小单位就是线程。协程是微线程,某种意义上就是线程的一种

  • 进程指的是正在运行的程序。各进程是相互独立的,

  • 另一方面,多个线程可以在同一个进程中运行,线程间也可以相互合作。所属于同一个进程的各线程,其内存空间是共享的,因此,多个线程可以访问相同的数据。这是一个优点,但同时也是一个缺点。

  • ❑ 管道(pipe)
    ❑ 消息(message)
    ❑ 信号量(semaphore)
    ❑ 共享内存
    ❑ TCP套接字
    ❑ UDP套接字
    ❑ UNIX域套接字

  • 管道是通过pipe系统调用创建一对文件描述符来进行通信的方式。所谓文件描述符,就是表示输入输出对象的一种识别符,

  • 其中消息用于数据通信,信号量用于互斥锁,共享内存用于在进程间共享内存状态。它们结合起来被称为sysvipc。

  • 套接字根据通信对象的指定方法以及通信的性质可以分为不同的种类,其中主要使用的包括TCP套接字、UDP套接字和UNIX域套接字三种。

  • 使用套接字进行通信,需要在事先设定好的连接目标处,通过双方套接字的相互连接创建一个通道。这个连接目标的指定方法因套接字种类而异,在使用最多的TCP套接字和UDP套接字中,是通过主机地址(主机名或者IP地址)和端口号(1到65535之间的整数)的组合来指定的。

  • 利用网络进行通信的协议(protocol)迄今为止已经出现了很多种,但其中一些因为各种原因已经被淘汰了,现在依然幸存下来的就是一种叫做TCP/IP的协议。TCP套接字就是“用TCP协议进行通信的套接字”的意思。

  • TCP是负责错误修恢复、数据再发送、流量控制等行为的高层协议,它是在一种更低层级的IP协议(即Internet Protocol)的基础之上实现的。

  • UDP则是User Datagram Protocol(用户数据报协议)的缩写。UDP实际上是在IP的基础上穿了一件薄薄的马甲,和TCP相比,它有以下这些不同点。

◆ Rack与Unicorn

  • Rack的基本原理,是将对Rack对象发送HTTP请求的“环境”作为参数来调用call方法,并将以返回值方式接收的请求组织成HTTP请求。反向代理(reverse proxy)是指将代理服务器配置在Web服务器一侧的网络中,实现Web服务器的缓存、安全性、负载均衡等功能的技术,也可以指配置了这种技术的服务器本身。(原书注)

  • 一般的套接字都是通过主机名和端口号来指定通信对象,而UNIX域套接字则是通过路径来指定的。

◆ 支撑大数据的数据存储技术

  • Hash(散列表)指的是通过创建键和值的配对,由键快速找到值的一种数据结构。

  • Atomicity(原子性)、Consistency(一致性)、Isolation (隔离性)和Durability(持久性)。

    • 所谓Atomicity,是指对于数据的操作只允许“全部完成”或“完全未做改变”这两种状态中的一种,而不允许任何中间状态。
    • 所谓Consistency,是指数据库的状态必须永远满足给定的条件这一性质
    • 所谓Isolation,是指保持原子性的一系列操作的中间状态,不能由其他事务进行干涉这一性质,由此可以保持隔离性而避免对其他事务产生影响。
    • 所谓Durability,是指当保持原子性的一系列操作完成时,其结果会被保存并且不会丢失这一性质
  • 有人提出了CAP原理,即在大规模环境中:
    ❑ Consistency(一致性)
    ❑ Availability(可用性)
    ❑ Partition Tolerance(分裂容忍性)

  • ACID无论在任何情况下都要保持严格的一致性,是一种比较悲观的模式。而实际上数据不一致并不会经常发生,因此BASE比较重视可用性(Basically Available),但不追求状态的严密性(Soft-state),且不管过程中的情况如何,只要最终能够达成一致即可(Eventually consistent)。这种比较乐观的模式,也许更适合大规模系统。说句题外话,一开始我觉得BASE这个缩写似乎有点牵强,但其实BASE(碱)是和ACID(酸)相对的,这里面包含了一个文字游戏。

◆ NoSQL

  • 所谓NoSQL,是一个与象征关系型数据库的SQL语言相对立而出现的名词,它是包括键-值存储在内的所有非关系型数据库的统称。

  • 一般认为关系型数据库在性能上存在极限,因为关系型数据库必须遵守ACID特性。

  • 由于存在这样的限定,就可以实现高速查询。而且,大多数NoSQL数据库都可以以键为单位来进行自动水平分割。

  • 因此大多数NoSQL数据库都遵循“BASE”这一原则。因此BASE比较重视可用性(Basically Available),但不追求状态的严密性(Soft-state),且不管过程中的情况如何,只要最终能够达成一致即可(Eventually consistent)。

  • 键-值存储数据库
    ❑ 面向文档数据库
    ❑ 面向对象数据库

  • 所谓面向文档数据库,是指对于键-值存储中“值”的部分,存储的不是单纯的字符串或数字,而是拥有结构的文档。面向文档数据库包括CouchDB、MongoDB以及各种XML数据库等。

  • MongoDB的结构分为数据库(database)、集合(collection)、文档(document)三层。MongoDB中不存在事务的概念,因此总是以最后写入的数据为准。这种方法的前提,是基于“同一个文档基本上不会被同时修改”这一预测,也就是一种乐观的(近似的)事务机制

◆ 用Ruby来操作MongoDB

  • MongoDB具有下列这些主要特点:
    ❑ 以JSON(JavaScript Object Notation)格式保存数据
    ❑ 不需要结构定义
    ❑ 支持分布式环境
    ❑ 乐观的事务机制
    ❑ 通过JavaScript进行操作
    ❑ 支持从多种语言进行访问

◆ SQL数据库的反击

  • 通常认为NoSQL是通过牺牲SQL和ACID特性来实现其性能的,然而性能问题与SQL和ACID是无关的。

  • 服务器上的处理,大致进行分类的话,主要有4个瓶颈,而对于这些瓶颈的应对就是决定性能的关键。这4个瓶颈具体如下。

    • 日志(Logging):为了防止磁盘崩溃等故障的发生,大多数关系型数据库都会执行两次写入。即向数据库执行一次写入,再向日志执行一次写入。
    • 事务锁(Locking):在对记录进行操作之前,为了防止其他线程对记录进行修改,需要对事务加锁。这也形成了一项巨大的开销。
    • 内存锁(Latching):Latch是门闩的意思,这里是指对锁和B树等共享数据结构进行访问时所需要的一种排他处理方式。
    • 缓存管理(Buffer Management):一般来说,数据库的数据是写入到固定长度的磁盘页面中的。对于哪个数据写入哪个页面,或者是哪个页面的数据缓存在内存中
  • 斯通布雷克认为,要实现高速的数据库系统,必须要消除上述所有4个瓶颈,而且上述瓶颈并非SQL数据库所固有的。

  • VoltDB最大的特征在于,它是一个内存数据库系统。也就是说,数据基本上是储存在内存中的。由于数据存储在内存中,缓存管理的问题就得以解决,而且由于不存在磁盘崩溃的情况,也就不需要日志了。这样一来,四个瓶颈中一下子就解决了两个。此外,VoltDB也提供了定期将数据写入文件的快照功能。即事务锁和内存锁又是如何解决的呢?在VoltDB中,数据库是分割成多个分区(partition)来管理的,对于每个分区都分配了一个独立的管理线程。也就是说,对分区的操作是单线程的,因此也就从根本上不需要用于实现排他处理的事务锁和内存锁机制了。

  • 虽然性能很高,但却很难掌控,从这个意义上来看,VoltDB可以说是数据库中的“方程式赛车”了。

◆ memcached和它的伙伴们

  • 所幸的是,对数据的访问具备“局部性”的特点。也就是说,一个操作中所访问的数据大多是可以限定范围的。即便数据的量很大,但大多数的操作都是仅仅对一部分数据进行频繁的访问,而几乎不会去碰其余的数据。

  • 提到缓存,往往会包含以下含义:
    ❑ 可以高速访问。
    ❑ 以改善性能为目的。
    ❑ 仅用于临时存放数据,当空间已满时可以任意丢弃多出来的数据。
    ❑ 数据是否存放在缓存中,不会产生除性能以外的其他影响。

  • 对memcached的不满主要有下列这些:
    数据长度:对键和值的长度分别限制在250B和1MB过于严格。越是大的数据查询起来就要花很长的时间,从这个角度来说,对这样的数据进行缓存的需求反而比较高。

  • 分布:一台服务器的内存容量是有限的,如果能将缓存分布到多台服务器上就可以增加总的数据容量。然而memcached并没有提供分布功能。

  • 持久性:顾名思义,memcached只是一个缓存,重启服务器数据就会丢失,且为了保持一定的缓存大小,还会自动舍弃旧数据。

  • 大家对memcached的不满主要体现在以下几个方面:
    ① 缺乏持久性
    ② 不支持分布
    ③ 数据长度有限

  • Redis

  • 内存型:数据库的操作基本都在内存中进行,速度非常快。
    支持永久化:上面提到数据库的操作是在内存中进行的,但是它提供了异步输出文件的功能。发生故障时,最后输出的文件之后的变更会丢失。虽然并不具备严格的可靠性,但却可以避免数据的完全丢失。

  • 支持分布:现行版本和memcached一样支持在客户端一侧实现对分布的支持。Redis Cluster分布层的开发还在计划中。具备服务器端复制功能。

  • 除字符串之外的数据结构:除了字符串,Redis还支持列表(list,字符串数组)、集(set,不包含重复数据的集合)、有序集(sorted set,值经过排序的集合)和散列表(hash,键-值组合)。

  • 高速:全面使用C语言编写的Redis速度非常快。

  • 原子性:由于Redis内部是采用单线程实现的,因此各命令都具备原子性

◆ 多核时代的编程

  • 摩尔定律
  • 分支预测是其中的一种方案。分支预测是利用分支指令跳转目标上的偏向性,事先对跳转的目标进行猜测,并执行相应的取出指令操作
  • 所谓投机执行,就是对条件分支后的跳转目标进行预测后,不仅仅是执行取出命令的操作,还会进一步执行实际的运算操作。
  • 所谓超线程,就是通过同时处理多个取出并执行指令的控制流程,从而将没有相互依赖关系的运算同时送入运算器中,通过这一手段,可以提高超标量的利用效率。
  • 第一个极限就是导线宽度。即便是电流本该无法通过的绝缘体,在微观尺度上也会有少量电子能够穿透并产生微弱的电流。这样的电流被称为渗漏电流,现代CPU中有一半以上的电力都消耗在了渗漏电流上
  • 精密电路中还会产生发热问题。电流在电路中流过就会产生热量,而随着电路的精密化,其热密度(单位面积所产生的热量)也随之上升。

◆ UNIX管道

  • 像这样由“执行记录”生成的程序,被称为脚本(script),这也是之后脚本语言(script language)这一名称的辞源。

  • UNIX进程都具有标准输入和标准输出(还有标准错误输出)等默认的输入输出目标,而Shell在启动命令时可以对这些输入输出目标进行连接和替换。

  • 多核环境是将任务分配给多个CPU来提高单位时间处理能力的一种手段。也就是说,只有当CPU能力成为处理瓶颈时,这一手段才能有效改善性能。

  • 阿姆达尔定律指出,并行性是存在极限的,因此只靠多核无法解决所有的问题

◆ 非阻塞I/O

  • 在缓冲机制中,有两种情况会产生等待。一种是当缓冲区为空时,需要等待数据到达缓冲区(读取时);另一种是在缓冲区已满时,需要等待缓冲区腾出空间(写入时)(图1)。这两种“等待”就相当于程序停止工作的“阻塞”状态

  • 说点正经的,在软件开发中,如果不更换硬件,还可以用以下方法来改善软件的运行速度:
    ❑ 采用更好的算法
    ❑ 减少无谓的开销
    ❑ 用空间来换时间

  • 我从软件开发中学会了如何提高效率,作为应用,总结出了下面几个方法:
    ❑ 减负
    ❑ 拖延
    ❑ 委派

  • 在有限的条件下,提高工作效率的最好方法就是减负。我们所遇到的大部分工作都可以分为三种,即非得完成不可的、能完成更好但并不是必需的,以及干脆不做为好的。

    • 减少不必要不紧急的工作,就能够更快地完成必要的工作,提高效率
    • 干脆拖到不能再拖为止,这样一来,工期肯定赶不上了,只好看看哪些工作是真正必需的,剩下的那些就砍掉吧
    • 按照紧急程度从高到低来完成任务的话,就可以进一步提高自己的工作效率。
    • 就是当以自己的能力无法处理某项任务时,转而借用他人的力量来完成的意思。如果说协作、协调、团队合作的话,大概比委派给人的印象要好一些吧。说起来,这和用多核代替单核来提升处理能力的方法如出一辙。
  • 多核的困难大概有下面几种:
    ❑ 任务分割
    ❑ 通信开销
    ❑ 可靠性

  • node.js是一种用于JavaScript的事件驱动框架。提到JavaScript,大家都知道它是一种嵌入在浏览器中、工作在客户端环境下的编程语言,而node.js却是在服务器端工作的。

  • 相对地,在事件驱动框架所提供的事件驱动编程中,不存在事先设定好的工作顺序,而是对来自外部的“事件”作出响应,并调用与该事件相对应的“回调函数”

◆ ZeroMQ

  • 所谓拖延,就是将工作分解成细小的任务,将无法马上着手的工作拖到后面再做,从而减少等待和无用的时间,提高整体的工作效率

  • 我们需要另一个策略,即委派。也就是说,将工作交给多个人,或者多个CPU来共同分担,从而提升整体的处理效率。

  • 能够活用多CPU的处理,基本上可以分解为下列步骤:
    (1) 数据分割、分配
    (2) 对已分配的数据进行并行处理
    (3) 将已处理的数据进行集约

  • 多进程方式同样是喜忧参半的,其特点正好和线程方式相反,即无法共享内存空间,但处理不会被局限在一台计算机上完成。但相比线程方式来说,我更加倾向于使用进程方式

    • 首先,要安全使用线程相当困难。相比共享内存带来的性能提升来说,由于状态的共享会导致一些偶发性bug,因此风险大于好处。
    • 其次,对性能提升带来的贡献,会受到该计算机中搭载的CPU核心数量上限的制约,因此其可扩展性相对较低。
  • 进程间通信的手段有很多种,其中具有代表性的有下列几种。
    ❑ 管道
    ❑ SysV IPC
    ❑ TCP套接字
    ❑ UDP套接字
    ❑ UNIX套接字

  • 管道通信只能用于具有父子、兄弟关系、可共享文件描述符的进程之间,因此只能实现同一台电脑上的进程间通信。

  • SysV IPC包括下列3种通信方式。
    ❑ 消息队列
    ❑ 信号量
    ❑ 共享内存

    • 消息队列是一种用于进程间数据通信的手段。管道只是一种流机制,每次写入数据的长度等信息是无法保存的,相对地,消息队列则可以保存写入消息的长度。
    • 信号量(semaphore)是一种带有互斥计数器的标志(flag)。这个词原本是荷兰语“旗语”的意思,在信号量中可以设定对某种“资源”同时访问数量的上限。
    • 共享内存是一块在进程间共享的内存空间。通过将共享内存空间分配到自身进程内存空间中(attach)的方式来访问。由于对共享内存的访问并没有进行排他控制,因此无法避免一些偶发性问题,必须使用信号量等手段进行保护
  • 套接字也是一种文件描述符,可进行一般的输入输出。尤其是可以使用select系统调用,在通常I/O的同时进行“等待”,这一点非常方便。
    ❑ 套接字在进程结束后会由操作系统自动释放,因此无需担心资源泄漏的问题。
    ❑ 套接字(由于其优秀的设计)从很早开始就被吸收进System V等系统了,因此在可移植性方面的顾虑较少。

  • 套接字分为很多种,其中具有代表性的包括:
    ❑ TCP套接字
    ❑ UDP套接字
    ❑ UNIX套接

《代码的未来》读书笔记相关推荐

  1. 读书笔记 | 墨菲定律

    1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...

  2. 读书笔记 | 墨菲定律(一)

    1. 有些事,你现在不做,永远也不会去做. 2. 能轻易实现的梦想都不叫梦想. 3.所有的事都会比你预计的时间长.(做事要有耐心,要经得起前期的枯燥.) 4. 当我们的才华还撑不起梦想时,更要耐下心来 ...

  3. 洛克菲勒的38封信pdf下载_《洛克菲勒写给孩子的38封信》读书笔记

    <洛克菲勒写给孩子的38封信>读书笔记 洛克菲勒写给孩子的38封信 第1封信:起点不决定终点 人人生而平等,但这种平等是权利与法律意义上的平等,与经济和文化优势无关 第2封信:运气靠策划 ...

  4. 股神大家了解多少?深度剖析股神巴菲特

    股神巴菲特是金融界里的传奇,大家是否都对股神巴菲特感兴趣呢?大家对股神了解多少?小编最近在QR社区发现了<阿尔法狗与巴菲特>,里面记载了许多股神巴菲特的人生经历,今天小编简单说一说关于股神 ...

  5. 2014巴菲特股东大会及巴菲特创业分享

     沃伦·巴菲特,这位传奇人物.在美国,巴菲特被称为"先知".在中国,他更多的被喻为"股神",巴菲特在11岁时第一次购买股票以来,白手起家缔造了一个千亿规模的 ...

  6. 《成为沃伦·巴菲特》笔记与感想

    本文首发于微信公众帐号: 一界码农(The_hard_the_luckier) 无需授权即可转载: 甚至无需保留以上版权声明-- 沃伦·巴菲特传记的纪录片 http://www.bilibili.co ...

  7. 读书笔记002:托尼.巴赞之快速阅读

    读书笔记002:托尼.巴赞之快速阅读 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<快速阅读>之后,我们就可以可以快速提高阅读速度,保持并改善理解嗯嗯管理,通过增进了解眼睛和大脑功能 ...

  8. 读书笔记001:托尼.巴赞之开动大脑

    读书笔记001:托尼.巴赞之开动大脑 托尼.巴赞是放射性思维与思维导图的提倡者.读完他的<开动大脑>之后,我们就可以对我们的大脑有更多的了解:大脑可以进行比我们预期多得多的工作:我们可以最 ...

  9. 读书笔记003:托尼.巴赞之思维导图

    读书笔记003:托尼.巴赞之思维导图 托尼.巴赞的<思维导图>一书,详细的介绍了思维发展的新概念--放射性思维:如何利用思维导图实施你的放射性思维,实现你的创造性思维,从而给出一种深刻的智 ...

  10. 产品读书《滚雪球:巴菲特和他的财富人生》

    作者简介 艾丽斯.施罗德,曾经担任世界知名投行摩根士丹利的董事总经理,因为撰写研究报告与巴菲特相识.业务上的往来使得施罗德有更多的机会与巴菲特亲密接触,她不仅是巴菲特别的忘年交,她也是第一个向巴菲特建 ...

最新文章

  1. matlab画CDF曲线
  2. Windows 10第四个大补丸来啦
  3. android高效加载网络图片
  4. mac 上的环境变量配置
  5. python开多少进程合适_用了python多进程,我跑程序花费的时间缩短了4倍
  6. Open WebRTC Toolkit实时视频分析系统
  7. CSS3实现烟花特效 --web前端
  8. 求1到100的数字累加和(python)
  9. C#LeetCode刷题之#671-二叉树中第二小的节点(Second Minimum Node In a Binary Tree)
  10. python中集合变量_详解python的变量缓存机制
  11. If you insist running as root, then set the environment variable RUN_AS_USER=root...
  12. BJ-100型矩形波导传输TE10模,测得波导中相邻两个电场波节点之间的距离为19.88mm
  13. win10鼠标灵敏度怎么调_和平精英,灵敏度到底怎么调?小编视频来教你!
  14. 电脑硬盘为什么要分区
  15. Java的企业级开发项目--OA办公系统
  16. outlook 日历共享_如何共享您的Outlook日历
  17. 【Flutter 实战】全局点击空白处隐藏键盘
  18. 有哪些常见的字节码指令?
  19. Hot Restart Hot Reload
  20. Frame::Frame(1)

热门文章

  1. 【JQuery】操作 DOM
  2. Unity 3D模型展示之控制标注
  3. 5V降压1.5V的几款极大电流IC,超简单的LDO芯片
  4. “2020年嵌入式软件秋招经验和对嵌入式软件未来的一点思考”
  5. 2个鸡蛋,100层楼问题;
  6. 关于uniapp cheneckbox复选框不显示对号的问题
  7. iphone的致命硬伤
  8. 史上最全的安卓WebView之WebSettings说明
  9. 关于拼板邮票孔制作规范
  10. 计算机问卷调查数据分析报告,调查问卷分析报告怎么写