Locks & Synchronization

You learned that threads share the same memory, which makes them fast. But that sharing has a dark side. Imagine two threads both trying to update the same number at the same time, like the count of items in a shopping cart:

  • Thread A reads the count: 5.
  • Thread B reads the count: 5, at the same time.
  • Both add 1 and write back 6.

Two items were added, but the count says 6 instead of 7. One update was lost. This kind of bug is called a race condition, and the fix is locks and synchronization. Let’s understand the problem, then the cure.

🎯 What is a Race Condition

A race condition happens when two or more threads touch the same shared data at the same time, and the result depends on who happens to go first. The threads are “racing,” and the answer comes out wrong or unpredictable.

The scary part:

  • It doesn’t fail every time. It only breaks when the timing lines up just wrong.
  • So the bug might appear once in a thousand runs, which makes it very hard to find.

The root cause is always the same: shared data being changed by more than one thread without any coordination. So we need a way to make threads take turns.

🔒 What is a Lock

A lock is a way to say “only one thread can touch this data at a time.” Before a thread changes the shared data, it must grab the lock. While it holds the lock, other threads have to wait. When it’s done, it releases the lock and the next thread goes.

Here’s the cart example fixed, with a lead-in to the idea. This makes sure only one thread updates the count at a time.

lock.acquire() # wait here until the lock is free, then take it
count = count + 1 # safe: no other thread can be here right now
lock.release() # let the next thread in

Reading that: a thread takes the lock, does its update while everyone else waits, then releases it. Now the two adds happen one after the other, so the count goes 5, 6, 7. Correct.

The protected part is the critical section

The bit of code between taking and releasing the lock is called the critical section. It’s the part where shared data is touched, so only one thread is allowed inside at a time. Keep it as short as possible.

🔄 What “Synchronization” Means

Synchronization is the general word for coordinating threads so they don’t step on each other. Locks are the most common tool, but the goal is always the same: make sure shared data is touched safely, in an orderly way.

The simple rule to remember:

  • If data is shared and can be changed, threads touching it must be synchronized.
  • If data is never changed (read-only) or never shared, you don’t need a lock.

So you only pay the cost of locking where it’s truly needed, around shared, changing data.

⚖️ The Costs of Locking

Locks fix race conditions, but they aren’t free. There are real trade-offs.

  • They slow things down. While one thread holds the lock, others wait instead of working. Too much locking and your threads spend their time waiting, not doing.
  • They can cause deadlocks. A deadlock is when two threads each hold a lock the other needs, so both wait forever. Nothing moves.

Here’s a picture of a deadlock, the classic trap.

Thread A

holds Lock 1

wants Lock 2

Lock 2

(held by B)

Thread B

holds Lock 2

wants Lock 1

Lock 1

(held by A)

Reading that: each thread is holding what the other needs, and neither will let go. Both are stuck. Avoiding deadlocks is a big part of writing safe concurrent code.

Lock as little as possible

Every lock is a place where threads wait, and a chance for a deadlock. So protect only the shared data that really needs it, and keep the locked section short. Over-locking makes your system slow and fragile.

⚠️ Common Mistakes and Misconceptions

A few things to keep straight:

  • “Race conditions always crash, so they’re easy to catch.” The opposite. They only show up when timing lines up wrong, so they can hide for ages and appear randomly.
  • “Just lock everything to be safe.” That kills performance and invites deadlocks. Lock only shared, changing data, and keep it short.
  • “Read-only data needs locks too.” Not if it never changes. Locks are for data that can be modified by more than one thread.

🧩 What You’ve Learned

Nice work. Here’s the recap:

  • ✅ A race condition is when threads touch the same shared data at once and the result comes out wrong.
  • ✅ A lock makes threads take turns, so only one touches the shared data at a time.
  • ✅ The protected code between taking and releasing a lock is the critical section; keep it short.
  • ✅ Locks cost performance (threads wait) and can cause deadlocks, so lock only what truly needs it.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What is a race condition?

    Why: A race condition is two or more threads changing the same shared data at the same time, so the result depends on timing and can be wrong.

  2. 2

    What does a lock do?

    Why: A lock makes threads take turns. While one holds the lock, others wait, so the shared data is touched safely one at a time.

  3. 3

    What is a deadlock?

    Why: In a deadlock, each thread holds what the other needs and neither lets go, so both are stuck forever.

  4. 4

    When do you NOT need a lock?

    Why: Locks are only needed around shared data that can change. Read-only or unshared data is already safe.

🚀 What’s Next?

You know how to keep shared memory safe. Now let’s see how systems manage lots of threads efficiently.

  • Thread Pools show how to reuse a set of threads instead of making new ones constantly.
  • Idempotency is a related idea for making repeated actions safe.

Get these down and you’ll understand how systems do many things at once without chaos.

Share & Connect