转载地址:[url]http://www.ibm.com/developerworks/cn/web/wa-clojure/index.html?ca=drs-[/url]
1、本地线程var。
注意,这里的操作需要用到绑定宏。这改变了 var 的线程本地值。
因此,在 place-offer 函数的执行范围内 droid 和 history 所指向的值将不同。
然而,在执行之外,该值没有变化。要记得,默认情况下在 Clojure 中所有事物都是不可变的(immutable)。
绑定 vars 允许在线程本地范围内对事物进行变更。如果任何其他线程想要读取该值,将看不到任何变化。
对于仅需要改变状态并将其作为执行离散任务的一部分时,这是完成任务的简单方法。如果想以其他线程可见的方式来变更状态,可能需要采用 Clojure 的 atoms。
2、简单同步atom
atom 是状态可变更的变量。其使用非常简单并且完全同步。换句话说,
如果调用了改变 atom 值的函数,那么当该函数结果返回时, 可以确保所有的线程都能看到新值。
3、ref的软事务处理
Clojure 的 refs 提供了其最强大的并发特色。那就是 Clojure 的 Software Transactional Memory (STM) 实现。
Refs 与 atoms 类似。与 atoms 对比,它通常只需要一行额外的代码,其主要优势是协调。
利用 refs,可在单个事务中改变多个对象的状态。该事务将具有原子性、一致性、并与 ACID 的 ACI 隔离(由于全在内存中,因此不具有持久性)。
这一隔离属性意味着任何观察者将会要么见到事务中的所有变更,要么一个也见不到。atoms 不会出现这种情况。
Refs 遵循与 atoms 相同的包装模式。place-offer 函数的执行从调用 dosync 开始。此函数包装一个事务。
它提供前面所提到的协调。它允许同时改变 droid 和 history,并了解不会有任何对数据的直接读取。
如同 atoms 一样,您可以取消引用,并在执行完函数后打印该值,验证该值已经改变。
(关于函数的副作用 :函数副作用有两种原因:
1、在函数过程体内出现了改变非函数局部变量的语句,使得函数的运行能够改变全局变量的值。
2、函数体内有转出函数体的过渡语句,常见的就是GOTO语句。
函数副作用的表现:设x是一个函数F的非局部变量,在函数体内,x值能够被改变。
故,对于x*F来讲,不满足交换律,即x*F=F*x不一定成立(不排除特殊成立的情况),这就能够产生许多问题)
此时,您可能想知道 STM 在此处到底是如何工作的。如果一个线程利用数量 25 来调用 place-offer 函数,
同时,另一个线程利用数量 22 调用同一函数,将会发生什么情况?
Clojure 会确保在事务中间该值没有发生改变,因此,如果事务到达了 dosync 块的结尾,
并且,STM 可看到在当前事务启动以来有另一事务已经完成了,那么当前事务将回滚并再次运行。
这使得,仅纯函数 —— 也就是不具有副作用的函数 —— 才能作为事务的一部分,因为函数可能将被运行多次。
Clojure 采用性能非常高的永久数据结构,来确保此类事务/回滚的效率。
(同步函数:执行后等待响应再继续执行下面代码.,
异步函数:执行后不等待响应就能继续执行下面代码)
Refs 和 atoms 都是同步函数。如果不必同步变更状态,那么,agents 将提供一些优势。
4、简单异步agents
您经常需要变更状态,但是不必等待其变更,或者如果可对多个线程进行变更时,不必关心其变更的顺序。
这是个通用的模式,并且 Clojure 提供了编程模型来予以解决:agents
1、本地线程var。
注意,这里的操作需要用到绑定宏。这改变了 var 的线程本地值。
因此,在 place-offer 函数的执行范围内 droid 和 history 所指向的值将不同。
然而,在执行之外,该值没有变化。要记得,默认情况下在 Clojure 中所有事物都是不可变的(immutable)。
绑定 vars 允许在线程本地范围内对事物进行变更。如果任何其他线程想要读取该值,将看不到任何变化。
对于仅需要改变状态并将其作为执行离散任务的一部分时,这是完成任务的简单方法。如果想以其他线程可见的方式来变更状态,可能需要采用 Clojure 的 atoms。
user=> (defstruct item :title :current-price)
#'user/item
user=> (defstruct bid :user :amount)
#'user/bid
user=> (def history ())
#'user/history
user=> (def droid (struct item "Droid X" 0))
#'user/droid
user=> (defn place-offer [offer]
(binding [history (cons offer history)
droid (assoc droid :current-price (get offer :amount))]
(println droid history)))
#'user/place-offer
user=> (place-offer {:user "Anthony" :amount 10})
{:title Droid X, :current-price 10} ({:user Anthony, :amount 10})
nil
user=> (println droid) ;这里可以看出droid的值并没有被改变。
{:title Droid X, :current-price 0}
nil
2、简单同步atom
atom 是状态可变更的变量。其使用非常简单并且完全同步。换句话说,
如果调用了改变 atom 值的函数,那么当该函数结果返回时, 可以确保所有的线程都能看到新值。
user=> (def droid (atom (struct item "Droid X" 0)))
#'user/droid
user=> (def history (atom ()))
#'user/history
user=> (defn place-offer [offer]
(reset! droid (assoc @droid :current-price (get offer :amount))))
#'user/place-offer
user=> (place-offer {:user "Anthony" :amount 10})
{:title "Droid X", :current-price 10}
user=> (println @droid) ;;这里算是另一个线程调用查看droid的状态
{:title Droid X, :current-price 10}
nil
3、ref的软事务处理
Clojure 的 refs 提供了其最强大的并发特色。那就是 Clojure 的 Software Transactional Memory (STM) 实现。
Refs 与 atoms 类似。与 atoms 对比,它通常只需要一行额外的代码,其主要优势是协调。
利用 refs,可在单个事务中改变多个对象的状态。该事务将具有原子性、一致性、并与 ACID 的 ACI 隔离(由于全在内存中,因此不具有持久性)。
这一隔离属性意味着任何观察者将会要么见到事务中的所有变更,要么一个也见不到。atoms 不会出现这种情况。
user=> (def droid (ref (struct item "Droid X" 0)))
#'user/droid
user=> (def history (ref ()))
#'user/history
user=> (defn place-offer [offer]
(dosync
(ref-set droid (assoc @droid :current-price (get offer :amount)))
(ref-set history (cons offer @history))
))
user=> (place-offer {:user "Tony" :amount 22})
({:user "Tony", :amount 22})
user=> (println @droid @history)
{:title Droid X, :current-price 22} ({:user Tony, :amount 22})
nil
Refs 遵循与 atoms 相同的包装模式。place-offer 函数的执行从调用 dosync 开始。此函数包装一个事务。
它提供前面所提到的协调。它允许同时改变 droid 和 history,并了解不会有任何对数据的直接读取。
如同 atoms 一样,您可以取消引用,并在执行完函数后打印该值,验证该值已经改变。
(关于函数的副作用 :函数副作用有两种原因:
1、在函数过程体内出现了改变非函数局部变量的语句,使得函数的运行能够改变全局变量的值。
2、函数体内有转出函数体的过渡语句,常见的就是GOTO语句。
函数副作用的表现:设x是一个函数F的非局部变量,在函数体内,x值能够被改变。
故,对于x*F来讲,不满足交换律,即x*F=F*x不一定成立(不排除特殊成立的情况),这就能够产生许多问题)
此时,您可能想知道 STM 在此处到底是如何工作的。如果一个线程利用数量 25 来调用 place-offer 函数,
同时,另一个线程利用数量 22 调用同一函数,将会发生什么情况?
Clojure 会确保在事务中间该值没有发生改变,因此,如果事务到达了 dosync 块的结尾,
并且,STM 可看到在当前事务启动以来有另一事务已经完成了,那么当前事务将回滚并再次运行。
这使得,仅纯函数 —— 也就是不具有副作用的函数 —— 才能作为事务的一部分,因为函数可能将被运行多次。
Clojure 采用性能非常高的永久数据结构,来确保此类事务/回滚的效率。
(同步函数:执行后等待响应再继续执行下面代码.,
异步函数:执行后不等待响应就能继续执行下面代码)
Refs 和 atoms 都是同步函数。如果不必同步变更状态,那么,agents 将提供一些优势。
4、简单异步agents
您经常需要变更状态,但是不必等待其变更,或者如果可对多个线程进行变更时,不必关心其变更的顺序。
这是个通用的模式,并且 Clojure 提供了编程模型来予以解决:agents
user=> (def history (agent ()))
#'user/history
user=> (def droid (agent (struct item "Droid X" 0)))
#'user/droid
nil
user=> (defn place-offer [offer]
(send droid #(assoc % :current-price (get offer :amount))))
user=> (place-offer {:user "Tony" :amount 33})
#<Agent@396477d9: {:title "Droid X", :current-price 0}>
user=> (await droid)
nil
user=> (println @droid)
{:title Droid X, :current-price 33}
nil