Clojure软件事务存储器

栏目: 编程语言 · Clojure · 发布时间: 6年前

内容简介:多核或多CPU使得并发的要求更加迫切,传统使用锁来管理并发,遗憾的是已被证明不太理想,因为它们经常导致死锁、饥饿、竞争和容易出错。在这篇文章中,我们将探讨如何利用Clojure的软件事务存储器(STM)来更好地利用现代硬件。并发难题的核心是状态问题,实质上,大多数编程语言都会使用多线程共享数据更新的方式管理状态。

多核或多CPU使得并发的要求更加迫切,传统使用锁来管理并发,遗憾的是已被证明不太理想,因为它们经常导致死锁、饥饿、竞争和容易出错。在这篇文章中,我们将探讨如何利用Clojure的软件事务存储器(STM)来更好地利用现代硬件。

Clojure的方式

并发难题的核心是状态问题,实质上,大多数编程语言都会使用多线程共享数据更新的方式管理状态。

Clojure提供不可变数据结构和语言级语义来解决这个状态的并发更新问题,通过对状态的托管引用实现安全并发​​,这是由称为软件事务内存(STM)的控制机制提供的。

STM是一种软件级设计,用于控制对内存中共享数据的访问,它的工作方式类似于数据库事务(但限于控制对内存数据的访问,而不是持久的数据存储)。

STM事务确保以原子方式完成状态更改 ,与事务关联的所有更改,一旦过程中有失败,则全部回滚已经做出的更改,此外,必须实现一致地进行更改,这意味着如果更改无法满足某些指定的约束,则事务也会失败,最后,更改必须以隔离的方式发生,对事务中的数据所做的所有更改仅对当前的线程是可见的。

Clojure STM使用多版本控制并发,它在事务开始之前获取数据的快照,对快照所做的更改对于外部世界(其他线程)是不可见的,直到提交更改成功完成事务。

Clojure提供了ref(reference)结构,用于管理允许同步和

协调更改的数据,对refs的所有修改必须在STM事务中发生,该事务是使用dosync语法指定的。

银行业务情景

让我们考虑典型的银行账户处理方案,在一个帐户甚至多个帐户上进行大量交易。但是,该帐户预计必须在所有这些交易中保持一致的平衡。

让我们创建一个函数,使用ref创建一个帐户,指定帐户名、帐号和初始起始余额。


(defn create-account [ account-name account-number balance ]
( ref { :account-name account-name
:account-number account-number
:balance balance
} :validator allowable-balance? ) )

(defn allowable-balance? [ { :keys [ balance ]}]
( or ( > balance 0 )
(throw (IllegalStateException. "Balance cannot be less than zero" ) )
))

此函数返回一个ref,它表示调用时的帐户。我们还添加了

验证器功能,以确保帐户余额始终大于零。(毕竟我们是银行。)

现在,让我们创建三个帐号。


(def first-account ( create-account "Robert" 300045 120 ) )
( def second-account (create-account
"Mike" 30046 500 ) )
( def third-account (create-account
"Rose" 30047 200 ) )

这将分别创建具有指定详细信息的三个帐户引用,现在,假设Mike决定做慈善事业并指示银行从他的账户转账到Robert账户200美元。

要做到这一点,必须做两件事:首先,我们需要将Mike的账户扣除200美元,然后将其存入Robert的账户。对于外界来说,这两个操作必须同时发生(原子),转移需要以这种一种方式进行:即从任何外部观察者的角度来看,被转移的资金一次只能在一个账户中。

为此,Clojure要求使用dosync在事务中实现此操作。

让我们定义一个名为make-transfer的函数,它将指定数量的资金从一个帐户转移到另一个帐户。

(defn make-transfer [ from-account to-account transfer-amount ]
(dosync
(alter from-account update-in [ :balance ] - transfer-amount )
(alter to-account update-in [ :balance ] + transfer-amount )))

现在,让我们发出转移:


(transfer second-account first-account 200 )
(println "first account -> " @first-account "Second account -> " @second-account )

这是我屏幕上显示的输出。

first account -> {:account-name Robert, :account-number 300045, :balance 320} Second account ->
{:account-name Mike, :account-number 30046, :balance 300}

但是,下面这样的转移失败了:

first account -> {:account-name Robert, :account-number 300045, :balance 320} Second account ->
{:account-name Mike, :account-number 30046, :balance 300}

出现错误:

CompilerException java.lang.IllegalStateException: Balance cannot be less than zero, compiling:(~/clojure/concurrency.clj:44:1)

我们可以在不同的线程上执行这两个操作,如下所示:

(future (make-transfer second-account first-account 100 ) )
(future (make-transfer second-account third-account 600) )

理解alter函数

alter函数用于原子地更新一个Ref对象,它由提供的ref和一个函数来调用,该函数将ref作为参数并返回一个值,返回值将用作ref的新值。因为Clojure允许在多个线程中同时执行多个事务,所以传给alter函数的ref的快照值如果与ref的当前值相同时才允许事务提交,否则所有更改都将是丢弃,并使用ref的最新值重试事务。这种无锁方法允许线程自由执行事务而不会被阻塞,包括那些只能读取的事务。

commute函数

为了限制alter function在提交时需要的重试次数,可以在函数应用程序的顺序无关紧要的情况下使用commute函数,如果在两个事务结束时,哪个线程是否首先提交本身无所谓,可以采用这个函数。

因此,银行转账的顺序并不重要的情况下,可以像下面这样实施转移功能:

(defn make-transfer-2 [ from-account to-account transfer-amount ]
(dosync
(commute from-account update-in [ :balance ] - transfer-amount )
(commute to-account update-in [ :balance ] + transfer-amount )
))

结论

Clojure是一种动态类型的函数式语言,它的设计考虑了并发性。


以上所述就是小编给大家介绍的《Clojure软件事务存储器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

机器消灭秘密

机器消灭秘密

安迪•格林伯格 (Andy Greenberg) / 王崧、王涛、唐禾 / 重庆出版社 / 2017-8-10 / 49.8

《机器消灭秘密》一书中,格林伯格深入研究并生动再现了那些拥有全能技术的网络安全魔术师,他们将任何企图染指个人隐私的所谓国家机密的保密性打得粉碎。这本精心组织的著作是对此题材感兴趣的读者的必读之书,即便现在你可能不感兴趣,将来也极有可能希望了解这些内容,因为任何人都会不可避免地置身其中。无论你是初涉电脑屏幕之后的虚拟战场的新生,还是经验丰富的维基解密观察家,本书都是不可多得的上乘之作,你总会在其中发......一起来看看 《机器消灭秘密》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试