Hello Clojure - Concurrency

Clojure是一门支持并发编程的语言,它提供了很多特性让我们非常的方便进行并发程序的开发。

Future

Future可以让我们在另一个线程去执行任务,它是异步执行的,所以调用了future之后会立即返回。譬如:

user=> (future (Thread/sleep 3000) (println "I'am back")) 
#object[clojure.core$future_call$reify__6736 0x7118d905 {:status :pending, :val nil}]
user=> (println "I'am here")
I'am here
nil
user=> I'am back

在上面的例子中,sleep会阻塞当前线程的执行,但是因为我们用了future,所以clojure将其放到了另一个线程中,然后继续执行下面的语句。

有时候,我们使用了future之后,还需要知道future的任务执行的结果,知识后就需要用defer来实现了。我们可以使用defer或者@来获取future的result,譬如:

user=> (let [result (future (println "run only once") (+ 1 1))]
  #_=> (println (deref result))
  #_=> (println @result))
run only once
2
2
nil

deref还可以支持timeout设置,如果超过了等待时间,就返回一个默认值。

user=> (deref (future (Thread/sleep 100) 0) 10 5)
5
user=> (deref (future (Thread/sleep 100) 0) 1000 5)
0

我们也可以使用realized?来判断一个future是否完成

user=> (realized? (future (Thread/sleep 1000)))
false
user=> (let [f (future)]
  #_=> @f
  #_=> (realized? f))
true

Delay

Delay可以让我们定义一个稍后执行的任务,并不需要现在立刻执行。

user=> (def my-delay
  #_=> (delay (let [msg "hello world"]
  #_=> (println msg)
  #_=> msg)))
#'user/my-delay

我们可以通过@或者force来执行delay的任务

user=> @my-delay
hello world
"hello world"
user=> (force my-delay)
"hello world"

Clojure会将delay的任务结果缓存,所以第二次delay的调用我们直接获取的是缓存结果。

我们可以将delay和future一起使用,定义一个delay操作,在future完成之后,调用delay,譬如:

user=> (let [notify (delay (println "hello world"))]
  #_=> (future ((Thread/sleep 1000) (force notify))))
#object[clojure.core$future_call$reify__6736 0x2de625f3 {:status :pending, :val nil}]
user=> hello world

Promise

Promise是一个承诺,我们定义了这个promise,就预期后续会得到相应的result。我们通过deliver来将result发送给对应的promise,如下:

user=> (def my-promise (promise))
#'user/my-promise
user=> (deliver my-promise (+ 1 1))
#object[clojure.core$promise$reify__6779 0x30dc687a {:status :ready, :val 2}]
user=> @my-promise
2

promise也跟delay一样,会缓存deliver的result

user=> (deliver my-promise (+ 1 2))
nil
user=> @my-promise
2

我们也可以将promise和future一起使用

user=> (let [hello-promise (promise)]
  #_=> (future (println "Hello" @hello-promise))
  #_=> (Thread/sleep 1000)
  #_=> (deliver hello-promise "world"))
Hello world

Atom

在并发编程里面,a = a + 1这条语句并不是安全的,在clojure里面,我们可以使用atom完成一些原子操作。如果大家熟悉c语言里面的compare and swap那一套原子操作函数,其实对Clojure的atom也不会陌生了。

我们使用atom创建一个atom,然后使用@来获取这个atom当前引用的值:

user=> (def n (atom 1))
#'user/n
user=> @n
1

如果我们需要更新该atom引用的值,我们需要通过一些原子操作来完成,譬如:

user=> (swap! n inc)
2
user=> @n
2
user=> (reset! n 0)
0
user=> @n
0

Watch

对于一些数据的状态变化,我们可以使用watch来监控,一个watch function包括4个参数,关注的key,需要watch的reference,譬如atom等,以及该reference之前的state以及新的state,譬如:

user=> (defn watch-n
  #_=> [key watched old-state new-state]
  #_=> (if (> new-state 1)
  #_=> (println "new" new-state)
  #_=> (println "old" old-state)))
user=> (def wn (atom 1))
#'user/wn
user=> @wn
1
user=> (add-watch wn :a watch-n)
#object[clojure.lang.Atom 0x4b28dcf8 {:status :ready, :val 1}]
user=> (reset! wn 1)
old 1
1
user=> (reset! wn 2)
new 2
2

Validator

我们可以使用validator来验证某个reference状态改变是不是合法的,譬如:

user=> (defn validate-n
  #_=> [n]
  #_=> (> n 1))
#'user/validate-n
user=> (def vn (atom 2 :validator validate-n))
#'user/vn
user=> (reset! vn 10)
10
user=> (reset! vn 0)

IllegalStateException Invalid reference state  clojure.lang.ARef.validate (ARef.java:33)

在上面的例子里面,我们建立了一个validator,参数n必须大于1,否则就是非法的。

Ref

在前面,我们知道使用atom,能够原子操作某一个reference,但是如果要操作一批atom,就不行了,这时候我们就要使用ref。

user=> (def x (ref 0))
#'user/x
user=> (def y (ref 0))
#'user/y

我们创建了两个ref对象x和y,然后在dosync里面对其原子更新

user=> (dosync 
  #_=> (ref-set x 1)
  #_=> (ref-set y 2)
  #_=> )
2
user=> [@x @y]
[1 2]
user=> (dosync 
  #_=> (alter x inc)
  #_=> (alter y inc))
user=> (dosync 
  #_=> (commute x inc)
  #_=> (commute y inc))

ref-set就类似于atom里面的reset!,alter就类似swap!,我们可以看到,还有一个commute也能进行reference的更新,它类似于alter,但是稍微有一点不一样。

在一个事务开始之后,如果使用alter,那么在修改这个reference的时候,alter会去看有没有新的修改,如果有,就会重试当前事务,而commute则没有这样的检查。所以如果通常为了性能考量,并且我们知道程序没有并发的副作用,那就使用commute,如果有,就老老实实使用alter了。

我们通过@来获取reference当前的值,在一个事务里面,我们通过ensure来保证获取的值一定是最新的:

user=> (dosync 
  #_=> (ref-set x (ensure y)))

Dynamic var

通常我们通过def定义一个变量之后,最好就不要更改这个var了,但是有时候,我们又需要在某些context里面去更新这个var的值,这时候,最好就使用dynamic var了。

我们通过如下方式定义一个dynamic var:

user=> (def ^:dynamic *my-var* "hello")
#'user/*my-var*

dynamic var必须用*包裹,在lisp里面,这个叫做earmuffs,然后我们就能够通过binding来动态改变这个var了:

user=> (binding [*my-var* "world"] *my-var*)
"world"
user=> (println *my-var*)
hello
user=> (binding [*my-var* "world"] (println *my-var*)
  #_=> (binding [*my-var* "clojure"] (println *my-var*)) (println *my-var*))
world
clojure
world

可以看到,binding只会影响当前的stack binding。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容