Creating a Lock with clojure.core/locking
Concurrency is a powerful but tricky beast. When multiple threads access shared resources, you need a way to prevent chaos.
I recently had an issue where double-clicking a button would process a request twice because the database was not updated
in time to prevent the process from happening a second time. My first thought was to put something to disable the button
from being able to be double-clicked, but after a conversation with my mentor, he brought up the scenario or two different
people on two different machines clicking the button at the same time. Enter clojure.core/locking
, a built-in tool
that makes thread synchronization in Clojure easy and elegant.
What is locking?
The locking macro ensures that only one thread can execute a block of code at a time, protecting shared resources from race conditions. It’s like a guard that says, “Wait your turn!”
How It Works
The syntax for locking is simple:
(locking resource
;; <your code>
...)
- resource: The object to lock on. Use any object that all threads can reference consistently.
- Critical section: Code that modifies shared resources or performs thread-sensitive operations.
A Simple Example
Let’s create a shared counter and safely increment it using locking:
(def counter (atom 0))
(def lock (Object.)) ; Create a lock object
(defn increment-counter []
(locking lock
(swap! counter inc)))
; Simulating multiple threads
(doseq [_ (range 10)]
(future (increment-counter)))
(Thread/sleep 100) ; Wait for threads to finish
(println "Final counter value:" @counter)
Here’s what happens:
- A shared lock object ensures only one thread at a time can enter the locking block.
- The critical section (swap! counter inc) safely increments the counter.
Why Use locking?
- Thread Safety: Prevents simultaneous access to shared resources.
- Simplicity: Minimal boilerplate compared to low-level synchronization primitives.
- Clojure-Friendly: Plays well with immutable data structures and atoms.