内容简介:多核或多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软件事务存储器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 针对亚马逊云存储器S3 BUCKET的渗透测试
- 【Logisim实验】构建立即数-随机存储器-寄存器的传送
- 用go语言来做嵌入式-读写铁电存储器
- 网关 Spring-Cloud-Gateway 源码解析 —— 路由(1.3)之 RouteDefinitionRepository 存储器
- 块存储、文件存储、对象存储三者之比较
- 云原生存储详解:容器存储与 K8s 存储卷
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Persuasive Technology
B.J. Fogg / Morgan Kaufmann / 2002-12 / USD 39.95
Can computers change what you think and do? Can they motivate you to stop smoking, persuade you to buy insurance, or convince you to join the Army? "Yes, they can," says Dr. B.J. Fogg, directo......一起来看看 《Persuasive Technology》 这本书的介绍吧!
MD5 加密
MD5 加密工具
RGB CMYK 转换工具
RGB CMYK 互转工具