OlegTheCat
OlegTheCat

Reputation: 4513

How to initialize atom in thread safe manner?

Let's say I have an atom:

(def my-atom (atom nil))

Then I initialize it as follows:

(defn init-atom [init-value]
  (when (nil? @my-atom)
    (reset! my-atom init-value)))

If init-atom is called concurrently from different threads, I race condition may occur. I'm looking for a way to safely and correctly initialize an atom. Anything there?

UPD:

Actually I'm initializing it as follows:

(defn init-atom [produce-init-fn]
  (when (nil? @my-atom)
    (reset! my-atom (produce-init-fn)])))

produce-init-fn may contain side effects.

Upvotes: 2

Views: 262

Answers (2)

Piotrek Bzdyl
Piotrek Bzdyl

Reputation: 13185

The following will make sure that the atom is initialized only once:

(defn init-atom [init-value]
  (swap! my-atom #(when (nil? %) init-value)))

Atom and swap! semantics guarantee that the function passed to swap! will be executed atomically.

If you pass a function producing the init value then it won't work as swap! might invoke the function multiple times in case of conflicting transactions. Then you need to use some kind of locking like in the other answer:

(let [o (Object.)]
  (defn init-atom [init-value-fn]
    (locking o
      (swap! my-atom #(when (nil? %) (init-value-fn))))))

init-value-fn still might be called more than once if there are other concurrent transactions with my-atom.

If you need to support lazy initialization and the init-value-fn is known upfront and the same for all the threads you can just wrap it into delay and then it will be called only once and its result will be cached and reused:

(def my-init-value (delay init-value-fn))

(defn init-atom []
  (swap! my-atom #(when (nil? %) @my-init-value)))

Upvotes: 4

Timothy Pratley
Timothy Pratley

Reputation: 10682

This should do the trick:

(let [o (Object.)]
  (defn init-atom [init-value]
    (locking o
      (when (nil? @my-atom)
        (reset! my-atom init-value)))))

Upvotes: 1

Related Questions