Overthunk

The 3 Goblins of Concurrency

Jul 13, 2020


Contents

What I learned Today

Chapter 9 is about the The Sacred Art of Concurrent and Parallel Programming.

(let [result (future (println "this prints once")
                     (+ 1 1))]
  (println "deref: " (deref result))
  (println "@: " @result))
(deref (future (Thread/sleep 1000) 0) 10 5)
;; returns 10 if future doesn't return a value within 10 milliseconds

The signature for this looks like - ([ref] [ref timeout-ms timeout-val])

Futures - chuck tasks onto other threads. Clojure allows you to treat task defn and requiring the result independently with delays and promises.

The Three Goblins of Concurrency

  1. First Concurrency Goblin Reference cell problem - occurs when two threads can read and write to the same location and the value of the location depends on the order of the reads and writes

  2. Second Concurrency Goblin Mutual Exclusion - each thread is trying to write something to file but doesn’t have exclusive write access. the output ends up being garbled because the writes are interleaved

  3. Third Concurrency Goblin Deadlock - each thread blocks indefinitely for a resource to become available

Delays - define a task without having to execute it or require the result indefinitely.

(def jackson-5-delay
  (delay (let [message "Just call my name and I'll be there"]
           (println "First deref: " message)
           message)))

Evaluate the delay and get result by deref or force

force is identical to deref but communicates the intent more clearly causing a task to start as opposed to waiting for it to finish.

(force jackson-5-delay)
=> "First deref: Just call my name and I'll be there"
=> "Just call my name and I'll be there"

@jackson-5-delay
=> "Just call my name and I'll be there"

One way you can use a delay is to fire off a statement the first time one future out of a group of related futures finishes.

Promises allow you to express that you expect a result without having to define the task that should produce it or when that task should run

(def my-promise (promise))
(deliver my-promise (+ 1 2))
@my-promise
  1. Future - Define a task and run it immediately on a different thread
  2. Delay - Define a task that doesn’t get executed until later
  3. Promise = Express that you require a result without having to know about the task that produces that result

Takeaways

Concurrency and Parallelism allow us to take advantage of modern computing hardware to design programs that run efficiently.

Clojure has a lot of abstractions and tools that allow us programmers to not bogged down by the three goblins of concurrency.

  1. Future - Define a task and run it immediately on a different thread
  2. Delay - Define a task that doesn’t get executed until later
  3. Promise = Express that you require a result without having to know about the task that produces that result

I didn’t complete the chapter exercises for this chapter but it sounded pretty interesting and I want to tackle them soon.