Programming Clojure学习笔记——并发

6.5 使用Vars管理线程本地状态
当你调用def或defn创建一个动态变量时,只是创建了一个变量。
当你传一个初始值给def时,该初始值就成了def创建变量的根绑定(root binding),如:
(def foo 10)
变量foo的根绑定被所有线程共享,验证如下:
当前线程中
user=> foo
10
新起的线程中
user=> (.start (Thread. (fn [] (println foo))))
user=> 10

你也可以通过binding宏为变量创建一个线程本地(thread-local)绑定:
(binding [bindings] & body)
绑定有动态作业域,换句话说,就是一个绑定在执行它的线程中从绑定的地方开始直到退出线程为止都可见,但对其他线程不可见。

举例说明:
(binding [foo 42] foo)
为了说明binding和let的不同创建一个简单的输出foo值的函数
user=> (defn print-foo [] (println foo))
#'user/print-foo
然后分别通过let和binding来调用print-foo
user=> (let [foo "let foo"] (print-foo))
10
user=> (binding [foo "bound foo"] (print-foo))
bound foo
从执行结果可以看出,let在let形式外就不起作用,因此还是输出10;而binding则从binding形式开始的任何向下调用链中都有效,因此输出"bound foo"。

远距离操作
动态绑定的变量有时被称为特殊变量,它们的名字以星号(*)开始和结尾,如标准输入输出流*in*,*out*和*err*。动态绑定可以实现远距离操作,当你改变动态绑定时,你在没有改变任何函数参数的情况下改变了远处函数的行为。
有一种远距离操作临时增强了函数的行为,在某些语言中被归为面向切面编程,但在Clojure中,只是动态绑定的一个副作用而已。
举例说明:
定义一个翻倍函数
user=>(defn slow-double [n]
   (Thread/sleep 100)
   (* n 2))
#'user/slow-double
user=> (defn calls-slow-double []
(map slow-double [1 2 1 2 1 2]))
#'user/calls-slow-double
统计计算用时
user=> (time (dorun (calls-slow-double)))
"Elapsed time: 600.298606 msecs"

下面对slow-double函数进行缓存
(defn demo-memoize []
   (time
     (dorun
         (binding [slow-double (memoize slow-double)]
            (calls-slow-double)))))
然后执行demo-memoize函数
user=> (demo-memoize)
"Elapsed time: 201.821212 msecs"
这个例子说明了远距离操作的强大功能和危险,通过动态绑定改变了calls-slow-double函数的行为。

与Java回调API一起工作
一些Java API依赖与回调事件处理,如GUI框架和XML解析器SAX。这些回调处理都是用可变对象写的,并且倾向于单线程。在Clojure中,实现回到处理的最好折中方式是动态绑定,引入类似变量的可变引用,又因为它们在但线程环境使用,因此不会出现并发问题。

最后总结以下Clojure的并发模型,如下:
模型        作用                函数类型
Refs和STM   并发同步更新        纯函数
Atoms       非并发同步更新      纯函数
Agents      非并发异步更新      任意
Vars        线程本地动态作业域  任意
Java锁      并发同步更新        任意
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值