这次来聊聊clojure的并行与并发,如果你还不知clojure为何物,请翻翻我的上一篇推文。“并行”是指clojure对并行计算的支持(parallel computing),“并发”是其并发特性(concurrency)。用通俗的话来说,“并行”是同一时间做多件事情,“并发”是同一时间应对多件事情。举个例子,“并行”就类似于GPU做3D绘图,左手画圆、右手画方;“并发”就类似于web 服务器利用服务器的多个内核来同时处理来自用户的多个请求。如果还不够明白,请大家google一下wiki。^_^

Clojure对并行计算支持的很好,而clojure的并发性其实一篇文章都写不完,因为它很有特色。clojure没有提供传统并发编程的元素,如:线程和锁。但clojure却提供了与线程和锁无关的、完全不同的4种并发编程模型,尽管你可以理解为这是clojure这么函数式语言基于java线程和锁的抽象。

Clojure对并行计算的支持

Clojure对并行计算的支持主要是通过并行类库与函数来提供支持,例如:reducer、pmap、pvalues、pcalls等,要注意的是:适用于CPU密集型任务,不是I/O密集或block的情形。reducer你没看错,的确是大数据的map-reduce的reducer,他们有联系?没有,但的确你可以把Clojure的并行计算包clojure.core.reducers用于大数据处理。想象一下,如果你需要处理一个大约 40G 的文件,对每一行文本进行解析并执行一些计算逻辑,最后写入数据库。你要怎么实现?是不是想到分而治之,但文件IO可能又是瓶颈,如何分割文件,然后放到内存,用clojure.core.reducers包来并行处理呢? 这是一个思路:先map后reduce。好了,回到正题。

举个栗子:Java如何对一个数列求和,代码是不是这样的:

public int sum(int[] numbers){

   int accumulator = 0;

   for(int n: numbers)

       accumulator += n;

   return accumulator;

}

Clojure是这样写:

(defn reduce-sum [numbers]

   (reduce (fn [acc x] (+ acc x)) 0 numbers))

这段代码用了clojure的reduce函数,其中3个参数:一个化简函数、一个初始值、一个集合。先用fn定义了一个匿名函数,接受两个参数并返回参数之和。然后后面的就是初始值和集合。其实,clojure有一个现成的函数+带代替fn这个匿名函数,所以代码可以继续简化:

(defn sum [numbers]

   (reduce + numbers))

上面都还没有涉及并行计算,现在,我们引入并行库clojure.core.reducers包,用里面的fold函数替换reduce:

(ns sum.core

   (:require [clojure.core.reducers :as r]))

(defn parallel-sum [numbers]

   (r/fold + numbers))

测试一下1加到一亿,性能提升2.5倍。背后是什么魔法?大家可以看clojure的官方说明和源码,它底层是用了JVM 原生的 fork/join 工具而进行的优化,看源码它默认起的java线程数为n+2(为什么?照例cpu密集型的线程池配置应该是对cpu密集型的为等于cpu数,I/O密集型的为更多),其主要实现思路:

  1. 分而治之(Partitioning the reducible collection at a specified granularity (default = 512 elements))

  2. 应用到各个分区(Applying reduce to each partition)

  3. 分区计算结果集合并(Recursively combining each partition using Java's fork/join framework.)

关于pmap等,此处不展开了。

Clojure的并发特性

前面提到:clojure没有提供传统并发编程的元素,如:线程和锁。但clojure却提供了与线程和锁无关的、完全不同的4种并发编程模型,尽管你可以理解为这是clojure这么函数式语言基于java线程和锁的抽象。这4种并发编程模型位:

  1. 线程本地vars(thread-local)

  2. 原子变量(atoms)

  3. 代理(agent)

  4. 引用(refs)和软件事务内存(ATM)

Clojure中所有的数据都是非易变的,除非用相应的Var、Ref、Atom和Agent类型明确表示某数据是易变的。这提供了管理共享状态的安全机制,对于这一点要深刻理解。而对于上面这4种并发编程模型,笔者在仔细研究之后发现,最简洁适用的是第二种,所以笔者具体展开第二种原子变量,其它几种有兴趣的朋友自己去研究,我想理解了第二种其它的应该都容易理解。

原子变量其实是在java.util.concurrent.atomic的基础上建立的。而java.util.concurrent.atomic背后其实用了CPU指令来实现的原子性保证,并使用了java.util.concurrent.atomicReference包提供的compareAndSet f方法,即CAS乐观比较重试法,所以内部没有锁。但java用cas也避免不了重试而clojure的原子变量为何能避免重试呢?原因就在于Clojure是函数式语言,其原子变量是无锁的,因为Clojure中所有的数据都是非易变的,是常量,它的值不是变化的,而是其数据结构被修改时总是保存了其之前的版本。

举个栗子,用原子map:

(def test (atom {}))

(swap! test assoc :username "paul")

(swap! test assoc :id 123)

再举一个管理运动员的web服务,这个代码有点多,但很好理解:

(def players (atom ()))

(defn list-players []

   (response (json/encode @players)))

(defn create-player [player-name]

   (swap! players conj player-name)

   (status (response "") 201))

(defroutes app-routes

   (GET "/players" [] (list-players))

   (PUT "/players/:play-name" [player-name] (create-player player-name)))

(defn -main [& args]

   (run-jetty (site app-routes) {:port 3000}))

最后说一下如何学习.....

经常有朋友问我如何自我提高,学习新知识?其实我们这一行要学习的东西真的很多,之前做无线的时候我还要学习客户端的东西(android/iOS/H5)和产品经理/UX的知识,现在要学docker看它的源码就要学Go语言。活到老,学到老嘛,stay hungry, stay foolish,永远把自己当成初学者,这样才能对新事物保持好奇,对所有观点持开放态度。

我建议的学习方法:

  1. 闭环学习:从浏览器、网络协议、webserver、数据库一个闭环,你都有了解吗?长的闭环链条能赋予你全面分析和解决问题的能力,很容易定位和分析问题,也有了自己的知识体系。

  2. 顺藤摸瓜:比如Socket -> UNIX网络编程 -> TCP/IP协议,顺藤摸瓜,往往会发现自己研究的越多,不懂的越多。才发现知道了自己不知道....

  3. 量变到质变:学了很多,实践了吗?学以致用,没用,就真的没用了。量变形成质变,没量不可能质变,代码没写几行?写了一万行没总结提炼和思考也不可能质变。就像我们今天学了clojure的是否可以总结一下各种并发编程模型? 多实践、多思考。

当然,前提是有兴趣,没有兴趣学习的话早点转行。

(原文发布与微信公众号 rayisthinking, 原文链接:http://mp.weixin.qq.com/s?__biz=MzAxNTQ4NTIzNA==&mid=208631556&idx=1&sn=d404833e167dc46868a26f43d09187a1#rd)

Clojure的并行与并发的更多相关文章

  1. C#并行编程-并发集合

    菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...

  2. Java 并行与并发

    Java 并行与并发 注意两个词:并行(Concurrent) 并发(Parallel) 并行:是逻辑上同时发生,指在某一个时间内同时运行多个程序 并发:是物理上同时发生,指在某一个时间点同时运行多个 ...

  3. c++11并行、并发与多线程编程

    首先,我们先理解并发和并行的区别. 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行. 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并 ...

  4. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)

    python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程 并行与并发 同步与异步 阻塞与非阻塞 CPU密集型与IO密集型 线程与进程 进 ...

  5. 《Go语言实战》摘录:6.1 并发 - 并行 与 并发

    6.1 并行 与 并发

  6. java面试一日一题:再谈垃圾回收器中的串行、并行、并发

    问题:请讲下java中垃圾回收器的串行.并行.并发 分析:该问题主要考察在垃圾回收过程中垃圾回收线程和用户线程的关系 回答要点: 主要从以下几点去考虑, 1.串行.并行.并发的概念 2.如何考虑串行. ...

  7. JVM_垃圾回收串行、并行、并发算法(总结)

    一.串行 JDK1.5前的默认算法 缺点是只有一个线程,执行垃圾回收时程序停止的时间比较长 语法 -XX:+UseSerialGC 新生代.老年代使用串行回收 新生代复制算法 老年代标记-压缩 示例图 ...

  8. 【转】golang中的并行与并发

    原文:http://blog.csdn.net/taohaoge/article/details/27970421 ------------------------------------------ ...

  9. python_并行与并发、多线程

    问题一: 计算机是如何执行程序指令的? 问题二: 计算机如何实现并发的? 轮询调度实现并发执行 程序1-8轮询完成,才再CPU上运行 问题三: 真正的并行需要依赖什么? 并行需要的核心条件 多进程实现 ...

随机推荐

  1. ng-bind,ng-cloak优化数据显示

    <div>{{text}}</div> 当我们使用angular在页面中有取值的时候,如果出现网络加载慢的问题,可能会在页面上出现{{text}}这种不好的体验,那么angul ...

  2. aix DNS 配置以及网络命令traceroute和nslookup 和 dig 命令

    DNS 域名系统 (DNS) 服务器将 IP 地址解释为其他计算机或网站的域名和地址.如果没有 DNS,您需要在 Web 浏览器中输入 IP 地址.例如,如果您未访问 DNS 并希望查看 IBM 的网 ...

  3. try-catch-finally的含有return使用揭秘

    很多人都会纠结这么一个问题try-catch-finally中有return的情况,我自己总结如下: 如果是值类型的话 请看代码 using System; using System.Collecti ...

  4. 水果项目第3集-asp.net web api开发入门

    app后台开发,可以用asp.net webservice技术. 也有一种重量级一点的叫WCF,也可以用来做app后台开发. 现在可以用asp.net web api来开发app后台. Asp.net ...

  5. ab压力测试和CC预防

    这两天从服务器上拉数据时,发现取回的数据不正确,而客户端当初健壮性不强,导致解析的数据为空. 起初以为是服务器维护的问题,可今天服务器登陆上了,发现还是数据不正确,正好昨天数据部的哥们告诉了我一个比较 ...

  6. LeetCode OJ-- Word Ladder II ***@

    https://oj.leetcode.com/problems/word-ladder-ii/ 啊,终于过了 class Solution { public: vector<vector< ...

  7. 1296: [SCOI2009]粉刷匠

    Description windy有 N 条木板需要被粉刷. 每条木板被分为 M 个格子. 每个格子要被刷成红色或蓝色. windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色. 每个 ...

  8. Piggy-Bank(HDU 1114)背包的一些基本变形

    Piggy-Bank  HDU 1114 初始化的细节问题: 因为要求恰好装满!! 所以初始化要注意: 初始化时除了F[0]为0,其它F[1..V]均设为−∞. 又这个题目是求最小价值: 则就是初始化 ...

  9. 开源IP代理池续——整体重构

    开源IP代理池 继上一篇开源项目IPProxys的使用之后,大家在github,我的公众号和博客上提出了很多建议.经过两周时间的努力,基本完成了开源IP代理池IPProxyPool的重构任务,业余时间 ...

  10. 写在Python前

    Python是用C编写的一种解释型语言,和shell一样,变量可以直接使用,而且就像C中的宏替换,但是Python同样支持进行底层的调用,可以很容易的和各种语言进行融合,俗称"胶水语言&qu ...