Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding STM properties in Clojure

Tags:

clojure

stm

I'm going through the book 7 concurrency models in 7 weeks. In it philosophers are represented as a number of ref's:

(def philosophers (into [] (repeatedly 5 #(ref :thinking))))

The state of each philosopher is flipped between :thinking and :eating using dosync transactions to ensure consistency.

Now I want to have a thread that outputs current status, so that I can be sure that the state is valid at all times:

(defn status-thread []
  (Thread.
    #(while true
      (dosync
        (println (map (fn [p] @p) philosophers))
        (Thread/sleep 100)))))

We use multiple @ to read values of each philosopher. It can happen that some refs are changed as we map over philosophers. Would it cause us to print inconsistent state although we don't have one?

I'm aware that Clojure uses MVCC to implement STM, but I'm not sure that I apply it correctly.

My transaction contains side effects and generally they should not appear inside a transaction. But in this case, transaction will always succeed and side effect should take place only once. Is it acceptable?

like image 665
Konstantin Milyutin Avatar asked Mar 04 '26 03:03

Konstantin Milyutin


1 Answers

Your transaction doesn't really need a side effect, and if you scale the problem up enough I believe the transaction could fail for lack of history and retry the side effect if there's a lot of writing going on. I think the more appropriate way here would be to pull the dosync closer in. The transaction should be a pure, side-effect free fact finding mission. Once that has resulted in a value, you are then free to perform side effects with it without affecting the STM.

(defn status-thread []
  (-> #(while true
         (println (dosync (mapv deref philosophers)))
         (Thread/sleep 100))
    Thread.
    .start)) ;;Threw in starting of the thread for my own testing

A few things I want to mention here:

  1. @ is a reader macro for the deref fn, so (fn [p] @p) is equivalent to just deref.
  2. You should avoid laziness within transactions as some of the lazy values may be evaluated outside the context of the dosync or not at all. For mappings that means you can use e.g. doall, or like here just the eagerly evaluated mapv variant that makes a vector rather than a sequence.
like image 174
Magos Avatar answered Mar 05 '26 17:03

Magos



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!