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上运行 问题三: 真正的并行需要依赖什么? 并行需要的核心条件 多进程实现 ...
随机推荐
- ng-bind,ng-cloak优化数据显示
<div>{{text}}</div> 当我们使用angular在页面中有取值的时候,如果出现网络加载慢的问题,可能会在页面上出现{{text}}这种不好的体验,那么angul ...
- aix DNS 配置以及网络命令traceroute和nslookup 和 dig 命令
DNS 域名系统 (DNS) 服务器将 IP 地址解释为其他计算机或网站的域名和地址.如果没有 DNS,您需要在 Web 浏览器中输入 IP 地址.例如,如果您未访问 DNS 并希望查看 IBM 的网 ...
- try-catch-finally的含有return使用揭秘
很多人都会纠结这么一个问题try-catch-finally中有return的情况,我自己总结如下: 如果是值类型的话 请看代码 using System; using System.Collecti ...
- 水果项目第3集-asp.net web api开发入门
app后台开发,可以用asp.net webservice技术. 也有一种重量级一点的叫WCF,也可以用来做app后台开发. 现在可以用asp.net web api来开发app后台. Asp.net ...
- ab压力测试和CC预防
这两天从服务器上拉数据时,发现取回的数据不正确,而客户端当初健壮性不强,导致解析的数据为空. 起初以为是服务器维护的问题,可今天服务器登陆上了,发现还是数据不正确,正好昨天数据部的哥们告诉了我一个比较 ...
- LeetCode OJ-- Word Ladder II ***@
https://oj.leetcode.com/problems/word-ladder-ii/ 啊,终于过了 class Solution { public: vector<vector< ...
- 1296: [SCOI2009]粉刷匠
Description windy有 N 条木板需要被粉刷. 每条木板被分为 M 个格子. 每个格子要被刷成红色或蓝色. windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色. 每个 ...
- Piggy-Bank(HDU 1114)背包的一些基本变形
Piggy-Bank HDU 1114 初始化的细节问题: 因为要求恰好装满!! 所以初始化要注意: 初始化时除了F[0]为0,其它F[1..V]均设为−∞. 又这个题目是求最小价值: 则就是初始化 ...
- 开源IP代理池续——整体重构
开源IP代理池 继上一篇开源项目IPProxys的使用之后,大家在github,我的公众号和博客上提出了很多建议.经过两周时间的努力,基本完成了开源IP代理池IPProxyPool的重构任务,业余时间 ...
- 写在Python前
Python是用C编写的一种解释型语言,和shell一样,变量可以直接使用,而且就像C中的宏替换,但是Python同样支持进行底层的调用,可以很容易的和各种语言进行融合,俗称"胶水语言&qu ...