惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google Online Security Blog
Google Online Security Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
C
Cybersecurity and Infrastructure Security Agency CISA
Cisco Talos Blog
Cisco Talos Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Scott Helme
Scott Helme
Project Zero
Project Zero
E
Exploit-DB.com RSS Feed
S
Secure Thoughts
K
Kaspersky official blog
L
Lohrmann on Cybersecurity
NISL@THU
NISL@THU
WordPress大学
WordPress大学
N
News and Events Feed by Topic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 热门话题
小众软件
小众软件
P
Privacy & Cybersecurity Law Blog
博客园 - 聂微东
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
About on SuperTechFans
Hacker News: Ask HN
Hacker News: Ask HN
AWS News Blog
AWS News Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Hacker News: Front Page
F
Full Disclosure
Latest news
Latest news
Schneier on Security
Schneier on Security
The Hacker News
The Hacker News
T
Troy Hunt's Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Jina AI
Jina AI
Martin Fowler
Martin Fowler
P
Proofpoint News Feed
TaoSecurity Blog
TaoSecurity Blog
G
GRAHAM CLULEY
Forbes - Security
Forbes - Security
V
V2EX - 技术
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Vulnerabilities – Threatpost
C
Cyber Attacks, Cyber Crime and Cyber Security
MongoDB | Blog
MongoDB | Blog
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
P
Privacy International News Feed
C
Check Point Blog
N
News and Events Feed by Topic

博客园 - Mainz

Top 20 Java Libries Used by Github's Most Popular Java Projects Why Apache Spark is a Crossover Hit for Data Scientists [FWD] Clojure上手 抽象之虚拟层 《微管理》读书笔记 如何设计优秀的API(转) Java开源框架推荐(全) Java性能提示(全) 国外程序员整理的 C++ 资源大全 (zt) 工程师成长道路语录(转) 在线画UML图的工具 6种有效的开发模型(转) 空降经理人的挑战 (原创) 搜狗高可用、高性能、可扩展商业平台设计(转) 几种线程池的实现分析(转) 常用的.NET开源项目(转) GC、LOH和Performance相关 Stackoverflow架构 RESTful SOA与DDD(领域驱动设计)
Clojure的并行与并发
Mainz · 2015-06-28 · via 博客园 - Mainz

这次来聊聊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)