作为其他值的容器,也都可以解引用。
deref不会阻塞。
add-watch
(add-watch reference key fn)
可以定义引用值发生改变时的回调,fn是4个参数 :key (atom/var/agent) 旧状态 新状态
1(def a (atom {})) 2 3(add-watch a :watcher 4 (fn [key atom old-state new-state] 5 (prn "-- Atom Changed --") 6 (prn "key" key) (prn "atom" atom) (prn "old-state" old-state) (prn "new-state" new-state))) (reset! a {:foo "bar"}) ;; "-- Atom Changed --" ;; "key" :watcher ;; "atom" #<Atom@4b020acf: {:foo "bar"}> ;; "old-state" {} ;; "new-state" {:foo "bar"} ;; {:foo "bar"}
watcher在每次修改时都会被调用,但是不保证真的有改变。因此可能需要自己比较新旧值
key用于给1个ref上添加多个不同watcher,移除watcher也要用key
(remove-watch reference key)
atom
用swap! 修改
1;; make an atomic list 2(def players (atom ())) 3;; #'user/players 4 5;; conjoin a keyword into that list 6(swap! players conj :player1) 7;;=> (:player1) 8 9;; conjoin a second keyword into the list 10(swap! players conj :player2) 11;;=> (:player2 :player1) 12 13;; take a look at what is in the list 14(deref players) 15;;=> (:player2 :player1)
swap! 接受函数 和参数,把atom里的作为第1个参数。后面参数不限
如果f的过程复杂,比如comp 几个函数计算时间会长, 在这期间atom的值可能被修改,而如果使用atom的老值计算,但是返回前atom改成了新值,此时,老值的计算结果会被放弃。
可以强行reset!
ref与STM
软件事务内存用于避免手工管理锁;
垃圾回收用于避免手工管理内存。
——自动化的手段封装那些领域无关,繁琐的细节
Clojure 满足了ACI 而D持久不是STM关心的,因为是纯内存。
alter commute ref-set 用于修改
书上例子是
角色:
1(defn character 2 [name & {:as opts}] 3 (ref (merge {:name name :items #{} :health 500} opts)) 4)
从1个角色身上移动1个装备到另一个角色身上
1(defn loot 2 [from to] 3 (dosync 4 (when-let [item (first (:items @from))] 5 (alter to update-in [:items] conj item) 6 (alter from update-in [:items] disj item) 7 ) 8 ) 9)
dosync表示一个事务
而when-let 如果[]里是nil 则不会执行body
disj从集合里去掉元素,返回剩余的集合
而alter,update-in @ 也都各有意义。
感觉读一块Clojure代码简直不是读代码,而是读古文。没有1个字多余,字认不齐,就1个完整句子都读不下来
alter: 记录ref在事务内第一个alter调用的值,按乐观方式提交,如果ref的值已经被别人改变,则整个事务失败。重试。
commute
可交换的计算,只关心最终结果
不会发生冲突,也不会重试,提高吞吐量,用ref最新值重算一遍
STM的事务如果耗时间太长,可能有活锁问题,总是提交失败(总有更快的任务来插队)。
write-skew 写偏差
在读、写之间 deref的其他值发生了改变。这时,把deref改成ensure,保证依赖的值被锁住,其他线程更新不了。

var
用def定义。var其实是ns级可变引用。不是“变量”
元数据修饰
私有和不可变
1(def ^:private X 42) 2(def ^:const X 42)
使用前声明
1(def j) 2;= #'user/j 3j 4;= #<Unbound Unbound: #'user/j>
主要是用于表示函数的占位符 ,比如引用其他位置的的公共API。
还可以直接用宏(declare j)表示这时使用前
动态作用域
1(def ^:dynamic *X*) 2(binding [*X* 500] 3 (...) 4)
习惯把这种变量两端加**,护耳。
给了函数一个隐式参数,在函数深层埋入需要改变的配置项。防止层层传递。
但坏处嘛,多少有点破坏封装的意思。
agent
每个agent维护1个action队列。
send在固定大小的线程池上执行,用于CPU密集任务
send-off不限制线程池大小,适合IO密集任务
