《七周七并发模型》是我在书店寻找Actor相关资料时偶遇的一本200多页的小册子。目录看似简单,实际的内容却涵盖了多种编程语言、并发模型与框架,对我实在是一大考验。断断续续地看了一个多月才勉强读完一遍,很粗浅地对书中提到的模型有了大体的了解。因为其中每一个模型都须要大量的基础知识支撑,很容易让本身陷入碎片化学习的泥沼,所以暂时我就把它当个入门的索引了。java
并行加速率=(Ws+Wp)/(Ws+Wp/p)
受计算中并行份量Wp、串行份量Ws、处理器数量p影响,加速化曲线呈S状梯形上升。算法
如前所述,因为涉及的语言和框架的跨度较大,每一个章节又相对独立、知识点相对零散,因此我没法从中窥探出一幅真正意义上的路线图,只能记下其中的聊聊数笔。编程
以哲学家进餐为例,谁同时拿上左右两支筷子才能吃上饭,其余人则只能等待他放下筷子才能机会拿齐一双筷子进餐。这就是线程与锁的故事——简单而粗暴。这一把把筷子就比如是一把把的锁,这锅饭就比如你们竞争的共享数据。没拿到筷子的人,即被阻塞在不停抢筷子的循环里——天知道我何时能抢到?安全
因为存在多把锁的同时竞争,很容易形成死锁。因而引入了一个简单而有效的规则:『始终按照一个全局的、固定的顺序获取多把锁』。好比说约定老是先取左手边的筷子,再取右手边的。因而获得以下的一个示意图。图中的每一个元素、每一个环节都须要进行同步化。数据结构
尽管问题获得初步解决,但内置锁在线程被阻塞后没法中断,形成线程假死,并且锁越多意味着死锁的风险也越大。因而引入可中断的锁、超时锁、条件变量等一些弥补机制。其中的条件变量condition机制,在Java 5.0和.NET 4.0以后的并发框架中获得了普遍应用。在引入Condition后,获得哲学家进餐问题的第二个版本:再也不是每支筷子对应一把锁,而改成视整张餐桌为一把锁,再把左右哲学家进餐结束视做竞争条件。这样,当左右两边的哲学家进餐结束时,就意味着本身能够进餐了。架构
private void eat() { // 先获取锁 table.lock(); try { // 若是条件知足就解锁并await一直等待 while (left.eating || right.eating) condition.await(); // 接收到其余线程经过signal()或signalAll()发出的信号 // 因而条件得以知足,加锁后访问共享资源 eating = true; } finally { table.unlock(); } } private void think() { table.lock(); try { eating = false; left.condition.signal(); right.condition.signal(); } finally { table.unlock(); } }
拆解问题,能够得出统计须要三个步骤:用下载的XML文本构造出若干张Page,而后逐页分析每张Page里出现的单词Word,最后合计每一个Word出现的次数。并发
在一般的串行化方案(逐行解析并统计)以后,依次给出了以下三个并发解决方案:框架
我对函数式编程FP的认识,就是y=f(x)
,对函数f给定x就必定获得y,不会由于f或者x持有其余的状态而产生不一样于y的其余结果。而在FP的世界,除了常量与递归成为常态外,最多见的就是map、reduce、fold等一些映射与聚合函数了。我只想说,FP的实现代码真是简洁得可怕!异步
(defn get-words [text] (re-seq #"\w+" text)) (defn count-words-sequential [pages] (frequencies (mapcat get-words pages)))
(defn count-words-parallel [pages] (reduce (partial merge-with +) (pmap #(frequencies (get-words %)) pages)))
(defn count-words [pages] (reduce (partial merge-with +) (pmap count-words-sequential (partition-all 100 pages))))
(defn parallel-frequencies [coll] (r/fold (partial merge-with +) (fn [counts x] (assoc counts x (inc (get counts x 0)))) coll))
这部分多数涉及Clojure的框架,因此我只关注了与FP密切相关的『持久数据结构』。async
理解持久数据结构,有点相似于理解『引用』与引用指向的『内存块』。而在FP里,纯粹的函数是不会修改既有结构的,由于它老是产生一个新的结果。
(def list_1 (1, 2, 3)) (def list_2 (cons 4 list_1)) (def list_3 (cons 5 (rest list_1)))
这段代码将产生下面这样的一个链表结构:
由此展现了CP与FP的一大重要区别:在CP中,一个变量既是标识Identity也是状态State,你在此时拿到某个列表是是(1,2,3),下一刻可能被别人改成(1,3,4,5),而在FP中则会始终是(1,2,3),这即是持久数据结构的本质。即一个标识,会对应多个版本、随时间变化的值。
“使用Actor就像租车——若是咱们须要,能够快速便捷地租到一辆;若是车辆发生故障,也不须要本身修理,直接打电话给租车公司更换一辆便可。”
每一个Actor,都是一个封闭的、有状态的、自带邮箱、经过消息与外界进行协做的并发实体。在Actors之间的消息发送、接收是并发的,可是在Actor内部,消息被邮箱存储后都是串行处理的。即Actor在同一时刻只会对一条 异步消息作出回应,从而回避锁策略。
使用Actor编程有个很不一样寻常的编程思想——“任其崩溃”!这是由于每一个Actor都被其监督者管理,这些不一样层次的Actor及其监督者搭建成一棵完整的Actor模型树。这棵树的叶结点是各类Actor,非叶的结点则是监督者。当某个Actor出现错误而崩溃时,由其监督者采起重启、忽略错误、记录缘由等措施。
消化掉以上两点,我就开始读《Reactive Messaging Patterns with the Actor Model》做为进阶了。最后,一样援引Smalltalk设计者、面向对象之父Alan Kay的一段话结束本节。好吧,我认可误入歧途了。
好久之前,我在描述“面向对象编程”时使用了“对象”这个概念。很抱歉,这个概念让许多人误入歧途,他们将学习重心放在了“对象”这个次要的方面。真正主要的方面是“消息”……建立一个规模宏大且能够生长的系统,关键在于其模块之间应如何交流,而不在于其内部的属性,以及行为应该如何表现。
CSP(Communicating Sequential Process,通讯顺序进程),和Actor比较相似。CSP不关心消息是谁发送的,只关心用于消息传递的那个通道Channel,我把这个Channel视做一个线程安全的消息队列,消息两端与消息队列自己是脱耦的。而在这方面,Actor模型中的消息两端是明确已知的,消息队列也是由Actor邮箱自带的。
CSP的执行体主要是各类Go块。这个Go块就当是一个状态机,这与C#中async和await的实现是一致的,具体参考《CLR via C#》第4版第649页『28.3 编译器如何将异步函数转换为状态机』。
这部分主要围绕矩阵、向量等线性代数方面所需的大量数值计算,引入OpenCL驱动GPU进行并行计算。这方面我没什么研究,直接跳过了。
这章我只知道Map-Reduce是Lambda的主要基石,将问题分解为Map和Reduce两个部分是一切的关键。其中,Map负责把输入映射为若干对key-value,而后由Reduce负责聚合这些key-value,输出最终数据。
除了Map-Reduce,为了解决报表与分析等一些须要及时反馈的信息,又引入了流处理技术。个人理解,就是对原始数据进行一个合理的分片,再利用批处理生成一个与报表需求一致的中间结果批处理视图,最后再借由服务层按需拼凑成最终结果。这个部分,合理的分片和拼凑算法是关键。
若是及时响应的要求还要更高,那么还有个加速层的东西,根据最后一次生成批处理视图的原始数据,直接生成相应的派生信息。这个部分,决定哪些数据过时、如何让其过时是关键。
分布式的世界,分布式的软件。