I wonder if I can get consistent reads only within transaction. Here is some code to illustrate the problem:
(def foo (ref 0))
(def bar (ref 0))
(defn incer [] (dosync (alter foo inc) (alter bar inc)))
(.start (Thread. (fn [] (last (repeatedly incer))))) ;; create a lot of action
Now I want to print the values of foo and bar
(println @foo @bar)
;=> 328498765 328498766
I know that I can get consistent values using ensure
(dosync (ensure foo) (ensure bar) (println @foo @bar))
;=> 356117587 356117587
I wonder if this is the only way or if there is a better solution. In his talk "Are we there yet?" (http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey) at min 55 Rich shows a slide that implies that there are ways to do it without putting the perception into a transaction, but I could not figure out how.
useRef: The useRef is a hook that uses the same ref throughout. It saves its value between re-renders in a functional component and doesn't create a new instance of the ref for every re-render. It persists the existing ref between re-renders. createRef: The createRef is a function that creates a new ref every time.
If you've used React, you'll be familiar with ref. It is easier to use, but you would need multiple refs to be attached to the component for certain use cases. In that situation, you might probably use a library.
Component renders and DOM updates And since changing ref doesn't cause the component to re-render, the button stays active. To demonstrate this point further, let's add a parent component. By default when you render a React component it recursively re-renders all its children.
You do need a transaction to get consistent reads, but you don't need ensure
:
;; will print consistent values
(dosync (println @foo @bar))
Ref reads in a transaction operate as if performed against a snapshot of the entire system state. If values consistent in this sense cannot be obtained, the transaction is retried. There is a history mechanism in place which stores a number of previous values for each Ref -- what number exactly is configurable on a per-Ref basis (see (doc ref)
), the default being that each Ref starts off not storing any history at all and each time it participates in a failed snapshot scenario it adds a new entry, up to a maximum of 10.
ensure
's purpose is to prevent write skew while allowing more concurrency than (ref-set some-ref @some-ref)
. To learn about write skew, see the Wikipedia page on Snapshot isolation. As a short summary, it is an anomaly which may occur when two or more transactions read overlapping sets of Refs and write to non-overlapping sets of Refs:
(def foo (ref 0))
(def bar (ref 0))
;; timeouts added to the transaction bodies
;; to make the demonstration reliable:
(future (dosync
(let [f @foo
b @bar]
(Thread/sleep 1000)
(if (zero? (+ f b))
(alter foo dec)))))
(future (dosync
(let [f @foo
b @bar]
(Thread/sleep 1000)
(if (zero? (+ f b))
(alter bar dec)))))
(println @foo @bar)
Here it is possible for both transactions to read both Refs before either transaction commits and happily proceed to decrement both Refs' values, whereas clearly any serial execution of the transactions would only decrement one value. Changing @bar
to (ensure bar)
and @foo
to (ensure foo)
would rule out this scenario.
(Actually (ensure bar)
is only needed in the transaction which does not modify bar
and (ensure foo)
is only needed in the transaction which does not modify foo
. In scenarios where ensure
is useful, it can be used in place of deref
/ @
, since (ensure foo)
returns the in-transaction value of foo
.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With