Search code examples
clojureatomic

Get the state of an atom, and reset it atomically


I'm writing a Mandelbrot Set implementation, and to speed up finding out which points go off into infinity, I decided to try using an ExecutorService to check the points in parallel.

Basically the plan is:

  • Calculate all the points I need to find
  • Give each point to the service
  • Have the service dump its results into an Atom wrapping a vector as they are produced
  • Have the drawing code periodically grab the produced results, and clear the queue

My problem is with the last point. How can I safely grab the previous results from an atom, and reset it?

I thought about the simple way of just:

(def draw-queue-A (atom []))

(defn grab-and-clear-queue []
  (let [results @draw-queue-A]
    (reset! draw-queue-A [])
    results)) 

But this looks unsafe. If something is added between the dereference and the reset!, it will be lost.

The atrocious abomination I've settled on at the moment is:

(defn grab-and-clear-queue []
  (let [results (atom [])]
    (swap! draw-queue-A
           (fn [res] (reset! results res)
                     []))
    results))

But using an atom just to retrieve the results seems ridiculous.

How can I sanely retrieve the contents of an atom, and reset it without potentially losing any results?


Solution

  • One simple answer is to use a Clojure ref instead of an atom. It allows you to lock the value for more than a single function invocation (unlike swap):

    (ns tst.clj.core
      (:use clj.core clojure.test tupelo.test)
      (:require [tupelo.core :as t] ))
    (t/refer-tupelo)
    (t/print-versions)
    
    (def results (ref []))
    (future
      (doseq [i (range 10)]
        (dosync
          (Thread/sleep 20)
          (alter results t/append i))))
    
    (defn get-and-clear []
      (dosync
        (let [curr-results @results]
          (ref-set results [])
          curr-results)))
    
    (doseq [i (range 10)]
      (Thread/sleep 50)
      (spyx (get-and-clear)))
    

    with results:

    -------------------------------------
       Clojure 1.8.0    Java 1.8.0_111
    -------------------------------------
    (get-and-clear) => [0 1]
    (get-and-clear) => [2 3]
    (get-and-clear) => [4]
    (get-and-clear) => [5 6]
    (get-and-clear) => [7]
    (get-and-clear) => [8 9]
    (get-and-clear) => []
    (get-and-clear) => []
    (get-and-clear) => []
    (get-and-clear) => []
    

    Other options would include using a queue from either clojure.core.async or a simple Java thread-safe queue.

    Depending on the format of your problem, you could also make use of a Python-style generator function as described here.