Clojure/并发编程学习
外观
一个 Ref,就像一个 Var,是一个用于存储另一个对象的存储单元,但 Ref 的目的却截然不同,它是用来存储并发共享数据的。
- Ref 只能在称为 事务 的代码块中修改。
- Ref 可以随时读取,包括在事务之外。
- 在一个事务中,其他线程对 Ref 的更改是不可见的。事务会看到 Ref 在事务开始时的值;如果事务本身更改了 Ref 的值,那么在事务的其余部分(或直到再次更改值)中会看到该值。
- 对 Ref 进行的事务更改,在事务完成并提交之前,其他线程是看不到的。
- 如果异常导致退出事务,则该事务中的更改将永远不会提交,因此会丢失。
- 如果在事务运行期间,另一个事务成功提交了该事务修改的 Ref,则事务提交将失败。
- 事务会自动重试,直到成功提交。(因此,事务通常应该是无副作用的,以免副作用执行多次。)
实际上,当两个在时间上重叠的事务修改同一个 Ref 时,第一个完成的事务将成功,而第二个将失败并重试。因此,共享数据的并发修改被排序成离散的代码块,每个代码块对该共享数据都有一个一致的视图。
但是,有时您希望 Ref 改变操作无论其他事务如何都能成功:通勤 操作将一个改变操作应用于 Ref,就像正常情况下一样,改变其在事务剩余时间内的值,但提交不会因为其他事务对该 Ref 的提交而失败:如果其他事务没有提交到 Ref,则该事务的局部 Ref 值将被提交;否则,事务局部值将被丢弃,而是将改变操作再次应用,这次应用于新的当前 Ref 值,并将结果值提交。在实践中,通勤操作应该是可交换的(因此得名):一系列可交换的操作可以以任何顺序应用以获得相同的结果。例如,递增计数器是可交换的,因此只有递增计数器的次数很重要,而递增操作的顺序并不重要。
与 Var 或 Ref 不同,Agent 始终只是一个单一的引用。Agent 仅通过发送到队列的请求进行修改,这些请求在线程池中异步处理,但保证按接收请求的顺序运行。这些请求中作用于代理的状态是由先前处理的请求建立的状态(不一定是在请求时建立的状态,因为当发出新的请求时,早期的请求可能仍在等待)。
Agent 可以随时读取,但读取的值是当前值,而不是由所有待处理请求完成而产生的值。如果您想要“最新”值,您可以等待 代理,这意味着您可以阻塞当前线程,直到代理上的所有待处理请求都完成(不包括在此阻塞期间发出的新请求)。
当在事务中发出请求时,请求不会在事务成功提交之前提交到队列。