Clojure的并行与并发

这次来聊聊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密集型的为更多),其主要实现思路:
分而治之(Partitioning the reducible collection at a specified granularity (default = 512 elements))
应用到各个分区(Applying reduce to each partition)
分区计算结果集合并(Recursively combining each partition using Java's fork/join framework.)
关于pmap等,此处不展开了。
Clojure的并发特性
前面提到:clojure没有提供传统并发编程的元素,如:线程和锁。但clojure却提供了与线程和锁无关的、完全不同的4种并发编程模型,尽管你可以理解为这是clojure这么函数式语言基于java线程和锁的抽象。这4种并发编程模型位:
线程本地vars(thread-local)
原子变量(atoms)
代理(agent)
引用(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,永远把自己当成初学者,这样才能对新事物保持好奇,对所有观点持开放态度。
我建议的学习方法:
闭环学习:从浏览器、网络协议、webserver、数据库一个闭环,你都有了解吗?长的闭环链条能赋予你全面分析和解决问题的能力,很容易定位和分析问题,也有了自己的知识体系。
顺藤摸瓜:比如Socket -> UNIX网络编程 -> TCP/IP协议,顺藤摸瓜,往往会发现自己研究的越多,不懂的越多。才发现知道了自己不知道....
量变到质变:学了很多,实践了吗?学以致用,没用,就真的没用了。量变形成质变,没量不可能质变,代码没写几行?写了一万行没总结提炼和思考也不可能质变。就像我们今天学了clojure的是否可以总结一下各种并发编程模型? 多实践、多思考。
当然,前提是有兴趣,没有兴趣学习的话早点转行。
(原文发布与微信公众号 rayisthinking, 原文链接:http://mp.weixin.qq.com/s?__biz=MzAxNTQ4NTIzNA==&mid=208631556&idx=1&sn=d404833e167dc46868a26f43d09187a1#rd)
Clojure的并行与并发的更多相关文章
- C#并行编程-并发集合
菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...
- Java 并行与并发
Java 并行与并发 注意两个词:并行(Concurrent) 并发(Parallel) 并行:是逻辑上同时发生,指在某一个时间内同时运行多个程序 并发:是物理上同时发生,指在某一个时间点同时运行多个 ...
- c++11并行、并发与多线程编程
首先,我们先理解并发和并行的区别. 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行. 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并 ...
- python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)
python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程 并行与并发 同步与异步 阻塞与非阻塞 CPU密集型与IO密集型 线程与进程 进 ...
- 《Go语言实战》摘录:6.1 并发 - 并行 与 并发
6.1 并行 与 并发
- java面试一日一题:再谈垃圾回收器中的串行、并行、并发
问题:请讲下java中垃圾回收器的串行.并行.并发 分析:该问题主要考察在垃圾回收过程中垃圾回收线程和用户线程的关系 回答要点: 主要从以下几点去考虑, 1.串行.并行.并发的概念 2.如何考虑串行. ...
- JVM_垃圾回收串行、并行、并发算法(总结)
一.串行 JDK1.5前的默认算法 缺点是只有一个线程,执行垃圾回收时程序停止的时间比较长 语法 -XX:+UseSerialGC 新生代.老年代使用串行回收 新生代复制算法 老年代标记-压缩 示例图 ...
- 【转】golang中的并行与并发
原文:http://blog.csdn.net/taohaoge/article/details/27970421 ------------------------------------------ ...
- python_并行与并发、多线程
问题一: 计算机是如何执行程序指令的? 问题二: 计算机如何实现并发的? 轮询调度实现并发执行 程序1-8轮询完成,才再CPU上运行 问题三: 真正的并行需要依赖什么? 并行需要的核心条件 多进程实现 ...
随机推荐
- 使用 IntraWeb (40) - 自定义 Session 数据
修改 UserSessionUnit 单元: unit UserSessionUnit; interface uses IWUserSessionBase, SysUtils, Classes, IW ...
- 【网站运营】网站被K的原因大总结
对于广大的站长来说网站被K或者是被降权是经常有的事情,不过我基本上还没有看见过Google的K站情况,也就是给网站降个权什么的处罚.如果你是用了很严重的作弊手段的话,那指定会是被Google给K掉的. ...
- 转自知乎:GitHub基本功能
作者:Fadeoc Khaos 链接:https://www.zhihu.com/question/20070065 来源:知乎 著作权归作者所有Github的基本功能: Repository:你和我 ...
- 1044. Shopping in Mars (25)
分析: 考察二分,简单模拟会超时,优化后时间正好,但二分速度快些,注意以下几点: (1):如果一个序列D1 ... Dn,如果我们计算Di到Dj的和, 那么我们可以计算D1到Dj的和sum1,D1到D ...
- Silverlight 上传文件源代码
public class FileUploadArgs : EventArgs { public string FileName { get; set; } public Exception Ex { ...
- ClickOnce的部署(.appref-ms)在软件限制策略中的解决方案
为了防止百度.360以及一些小厂商在妈妈的电脑里乱安装各种程序,在域中开启了软件限制策略. 今天在用Github的Windows客户端时发现由于软件限制策略无法运行. Github在Windows中采 ...
- UVA 11992 Fast Matrix Operations(线段树:区间修改)
题目链接 2015-10-30 https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=s ...
- Web开发者宝典:10款流行前沿矢量图形素材
矢量图形以其鲜亮.无杂斑和醒目的外观而深受网页设计师们的喜爱.本文整理了网页设计中最为流行的20款矢量设计素材,如网页按钮,社交媒体图标和联系人图标等,希望Web开发人员会喜欢. 1. Web But ...
- Exception in thread "http-apr-8080-exec-2"
设置了catalina.bat.catalina.sh都不起作用MyEclipse中选择菜单Windows---preferences---MyEclipse---Servers---Tomcat-- ...
- java编程规范
一.规范存在的意义 应用编码规范对于软件本身和软件开发人员而言尤为重要,有以下几个原因: 1.好的编码规范可以尽可能的减少一个软件的维护成本 , 并且几乎没有任何一个软件,在其整个生命周期中,均由最初 ...