views:

401

answers:

3

What i the difference between Clojure STM ( dosync) approach and Java synchronize Block ?

Im reading the code below from "The sleeping barber" problem. (http://www.bestinclass.dk/index.clj/2009/09/scala-vs-clojure-round-2-concurrency.html)

(defn the-shop [a]  
  (print "[k] entering shop" a)  
  (dosync     
    (if (< (count @queue) seats)  
      (alter queue conj a)  
      (print "[s] turning away customer" a))))

To avoid race conditions, dosync is used, so i ask myself " What is the diference (STM) from Java synchronize block " ? Is it will block this critical code ?

Thanks in advance ! Dantas

+11  A: 

dosync and synchronized give access to completely different concurrency abstractions.

synchronized is a way of acquiring and releasing locks. When a thread enters a synchronized block, it attempts to acquire the appropriate lock; if the lock is currently held by a different thread, the current thread blocks and waits for it to be released. This leads to certain problems, such as the risk of deadlock. The lock is released when the thread leaves the synchronized block.

dosync marks a block of code which is to be run in a transaction. Transactions in Clojure are a way of coordinating changes to Refs (objects created with the ref function); if you need some code to have a consistent view of some pieces of mutable state in Clojure -- and possibly change them -- you put those in Refs and execute your code in a transaction.

A transaction has the interesting property that it will restart if for some reason it cannot commit, up to a certain maximal number of retries (currently hard-coded to be 10000). Among the possible reasons for a transaction being unable to commit are an inability to obtain a consistent view of the world (actually, the relevant Refs -- there is an "adaptive history" facility which makes this less of a problem than it might seem at first glance); simultaneous changes made by other transactions; etc.

A transaction runs no risk of being deadlocked (unless the programmer goes out of their way to introduce a deadlock unrelated to the STM system through Java interop); livelock, on the other hand, is a certain possibility, though it is not very probable. In general, many -- although not all! -- of the intuitions programmers associate with database transactions are valid in the context of STM systems, including that of Clojure.

STM is a huge topic; one excellent resource for learning about Clojure's STM is Mark Volkmann's Software Transactional Memory article. It goes into great depth in discussing Clojure's STM in its final sections, but the beginning can serve as great introductory reading.

As for the snippet you quoted, it's actually not something you would normally want to emulate in production code, since dosync blocks should almost always be side-effect free; the print here can be useful for demonstrating the inner working of the STM, but if you wanted a transaction to cause side-effects in real code, you should have it spawn a Clojure Agent for the purpose (which would only execute its task if the transaction successfully commits).

Michał Marczyk
Michal , u wrote "since dosync blocks should almost always be side-effect free" . a console print will break this "side-effect" property ? so update a database table will do the same, all right ? What is supposed be inside a dosync block ? Thanks again, excellent post !
CHAPa
Right, printing out to the console and updating a database table both qualify as side effects. `dosync` blocks are in general not supposed to contain side-effecty code *except* calls to transaction-aware Ref-modifying functions (`alter` / `commute` / `ref-set`), with the important exception that any actions sent to Agents may contain side effects meant to happen after the transaction completes (all such actions will be performed if and when the transaction commits, so there is no risk of causing the same side effect more then once).
Michał Marczyk
So, if you needed to modify the values of a couple of Refs and update your database, your `dosync` would contain `alter` / `commute` calls to change the Refs and a `send` / `send-off` telling an Agent to perform the db update once the transaction commits (and so no longer runs the risk of retrying or failing due to the retry limit being reached).
Michał Marczyk
Oh, also, about the "almost" -- debug printouts in transactions can be as useful as anywhere else (in particular, they will tell you how many times your transaction needed to retry, as Lau's "sleeping barber" code nicely demonstrates); calls to memoised functions from within transactions should of course cause the side effect of recording any newly computed values; etc. Still, one should be really sure of what one's doing when introducing side effects inside `dosync` -- lots of breakage can result from a lack of care on this point.
Michał Marczyk
Thanks Michał. Excellent view ! Really nice !
CHAPa
When you `send-off` an agent from within a `dosync`, it doesn't run until the `dosync` ends. This means it's too late to rollback the STM transaction if something goes wrong in your agent, which is always possible if your agent is accessing databases (the db might reject your data or the server might be down). I think coordinating a ref and a database is currently very hard (or impossible?) even using agents.
Brian Carper
Oh, that is absolutely true...! Thanks for the comment. This just goes to confirm my suspicion that my STM-fu is a bit rusty...
Michał Marczyk
+2  A: 

Also in addition to Michał's excellent answer, with STM transactions, reads always get you the frozen value at the beginning of the transaction and need not wait for any ongoing transaction to complete.

Ramakrishnan Muthukrishnan
Yes ! Read operations do not requires lock. it is lock free. when u read, u get a snapshot. But if u write, the operation only commit if the snapshot is equal from what u have. if when u try write the data isn´t the same, the operation rollback.
CHAPa