Locks vs Concurrency Primitives

Summary: Many people have asked me why Clojure has concurrency primitives. Aren't locks good enough? A humorous metaphor is elaborated.

I've presented Clojure to small conferences before. When I start talking about using the Clojure concurrency primitives and how they help you write multi-threaded code, invariably, I mean every time, I get someone who asks "Why not just use synchronized?" or "Can't I just use locks?". It's an honest question so I'll do my best to answer it. And of course, I'll have to use a nice metaphor.

Let's imagine a house. It's a nice big house. But it only has one bathroom. And that's ok when there's one person living in the house. That person is free to use the bathroom whenever they please, for as long as they please. They might even leave the door open because it's their house and nobody is around.

Then into that house we add another person, let's say a stranger, just to make it a little awkward. Hmm. That one bathroom is getting a little contention. A couple of times, a roommate opened the door on the other. Uh oh! Awkward! What's the solution? Right, a lock!

Now, whenever one of them has to go to the bathroom, they open the door. At least they try, because it might be locked. If it's locked, well, they try again later. But if it's open, they go right in, lock the door, and get to business. When they're done, they open the lock and leave. Problem solved!

Well, not really. You see, I didn't tell you, but this whole setup is actually a reality TV show. Season 1 had one person in the house. Season 2 had 2 people in the house. Now it's time for Season 3 of 2^n Beds/1 Bath!! Season 3 has 4 roommates sharing a bathroom. (In Season 3, n=2. Bare with me!)

Season 3 is a lot more entertaining. The lock sure works for keeping the door closed while you're showering, but there are three people outside who need to get ready to go to work. It's clear after a week of jiggling the doorknob that the lock is not enough. It's essential but it's not going to cut it. The roommates decide to get organized. They coordinate their hygiene schedules and lay down some ground rules.

  1. Be courteous to each other. Don't do unnecessary stuff in the bathroom. Others might be waiting.
  2. Only shower during your allotted time.

Amazingly, with a simple whiteboard calendar and a couple of rules, the bathroom sharing problems go away. That is, until season 4!

In season 4, 4 new roommates show up for a total of 8. What chaos! With 4 people, a bit of coordination and some goodwill was all they needed. But with 8, the bathroom started to get messy. It was impossible. At all hours of the day you would see two or three people doing the pipi dance outside of the bathroom. Then when the door opened they all jumped at it and pushed and pulled and shoved to get in there. One poor person waited for four hours because they were just unlucky enough. They would wait for the door to open, but when it did somebody would jump in front and close the door. Good thing there was a lock or nobody would have had a turn in the bathroom. But it clearly wasn't enough.

The roommates got together for a meeting and devised a new, more sophisticated way to coordinate. The schedule was still there, but now there was a queue and tokens to represent each roommate. If you wanted to use the bathroom and the door was closed, you'd put your token into the queue and you could go back to what you were doing. When someone finished in the bathroom, they'd take the next token off the queue, shout the name, and you had 30 seconds to claim your spot!

Ok, it's getting complicated but that worked for those 8. I don't even want to explain how they cleaned it. And just wait till next season when 16 people shared the bathroom. The drama. The humor. The smells!

So, finally, we get to the point: locks are good. I occasionally use them in Clojure. But once things start really getting complicated, like they do in real world scenarios with lots of threads (and of course it's only getting more complicated with more and more cores every year), well, at that point you need more than locks.

We use systems and rules in the real world to communicate and coordinate shared resources all the time. To say we just need locks is to throw away the notion of the line at the bank, the "take a number" system at the bakery, or the reservation system at a restaurant.

Clojure gives you some concurrency primitives that implement the rules and systems that the metaphorical roommates used. There are atoms, refs, and agents so you don't have to implement them yourself. And don't forget about core.async, the mother of all concurrency primitives, that can save you from callback hell.

Well, I hope that answers the question. And I hope you got a laugh. The next time you're wondering about whether you need anything more than locks, remember that a bathroom queue is a great concurrency primitive. But don't forget to lock the door!