Bird
Raised Fist0
Rubyprogramming~10 mins

Thread synchronization with Mutex in Ruby - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Thread synchronization with Mutex
Start Threads
↓
Thread tries to lock Mutex
↓
Access shared
↓
resource
↓
Unlock Mutex
↓
Threads finish
Threads try to lock a mutex before accessing shared data. If locked, others wait. After use, mutex unlocks allowing next thread.
Execution Sample
Ruby
require 'thread'

mutex = Mutex.new
counter = 0

threads = 2.times.map do
  Thread.new do
    mutex.synchronize do
      temp = counter
      sleep(0.1)
      counter = temp + 1
    end
  end
end
threads.each(&:join)
puts counter
Two threads safely increment a shared counter using a mutex to avoid conflicts.
Execution Table
StepThreadActionMutex Locked?counter ValueNotes
1Thread 1Attempts to lock mutexNo0Mutex is free, Thread 1 locks it
2Thread 1Reads counter into tempYes0temp = 0
3Thread 1Sleeps (simulate work)Yes0Holding lock during sleep
4Thread 1Sets counter = temp + 1Yes1counter updated to 1
5Thread 1Unlocks mutexNo1Mutex released
6Thread 2Attempts to lock mutexNo1Mutex free, Thread 2 locks it
7Thread 2Reads counter into tempYes1temp = 1
8Thread 2Sleeps (simulate work)Yes1Holding lock during sleep
9Thread 2Sets counter = temp + 1Yes2counter updated to 2
10Thread 2Unlocks mutexNo2Mutex released
11MainPrints counterNo2Final counter value is 2
💡 Both threads completed, mutex unlocked, counter incremented safely to 2
Variable Tracker
VariableStartAfter Thread 1After Thread 2Final
counter0122
mutex Locked?NoNoNoNo
Key Moments - 3 Insights
Why does the counter not become 1 instead of 2 after both threads run?
Because each thread locks the mutex before reading and updating counter, they run one after another safely (see execution_table steps 1-5 and 6-10). This prevents race conditions.
What happens if a thread tries to lock the mutex while it is already locked?
The thread waits (blocks) until the mutex is unlocked by the other thread, as shown in the flow where one thread locks and the other waits.
Why do we use mutex.synchronize instead of manually locking and unlocking?
mutex.synchronize ensures the mutex is always unlocked even if an error occurs inside the block, making code safer and simpler (see code sample).
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table, what is the value of counter after Thread 1 unlocks the mutex?
A0
B1
C2
DUndefined
💡 Hint
Check step 5 in the execution_table where Thread 1 unlocks the mutex and counter is updated.
At which step does Thread 2 acquire the mutex lock?
AStep 9
BStep 2
CStep 6
DStep 11
💡 Hint
Look at the 'Mutex Locked?' column in the execution_table for Thread 2 locking the mutex.
If we remove the mutex, what problem would most likely happen?
ARace condition causing incorrect counter value
BThreads run sequentially without issues
CCounter increments correctly to 2
DProgram crashes immediately
💡 Hint
Think about why mutex is used in the code sample and what happens if threads access shared data simultaneously.
Concept Snapshot
Mutex ensures only one thread accesses shared data at a time.
Use mutex.synchronize { ... } to lock and unlock safely.
Threads wait if mutex is locked, preventing race conditions.
Without mutex, shared data can be corrupted.
Mutex unlocks automatically after block ends.
Full Transcript
This example shows two threads incrementing a shared counter safely using a mutex. Each thread locks the mutex before reading and updating the counter, then unlocks it. The execution table traces each step, showing when threads lock, read, sleep, update, and unlock. The variable tracker shows counter changes from 0 to 2. Key moments clarify why mutex prevents race conditions and why synchronize is safer. The quiz tests understanding of mutex locking steps and consequences of removing mutex.

Practice

(1/5)
1. What is the main purpose of using a Mutex in Ruby threads?
easy
A. To create new threads automatically
B. To prevent multiple threads from accessing shared data at the same time
C. To speed up thread execution by running them in parallel
D. To stop threads from running

Solution

  1. Step 1: Understand what shared data means in threads

    Shared data is information that multiple threads can access or change.
  2. Step 2: Identify the role of Mutex

    Mutex ensures only one thread changes shared data at a time to avoid errors.
  3. Final Answer:

    To prevent multiple threads from accessing shared data at the same time -> Option B
  4. Quick Check:

    Mutex protects shared data = prevent simultaneous access [OK]
Hint: Mutex locks shared data to avoid conflicts between threads [OK]
Common Mistakes:
  • Thinking Mutex speeds up threads
  • Believing Mutex creates threads
  • Assuming Mutex stops threads completely
2. Which of the following is the correct way to use a Mutex to protect a critical section in Ruby?
easy
A. mutex.synchronize { critical_code }
B. mutex.lock; critical_code
C. mutex.run(critical_code)
D. mutex.start { critical_code }

Solution

  1. Step 1: Recall Mutex usage in Ruby

    Ruby's Mutex class provides a synchronize method to wrap critical code safely.
  2. Step 2: Compare options

    Only mutex.synchronize { critical_code } is the correct and safe syntax.
  3. Final Answer:

    mutex.synchronize { critical_code } -> Option A
  4. Quick Check:

    Use synchronize method to protect code [OK]
Hint: Use mutex.synchronize { } to wrap critical code [OK]
Common Mistakes:
  • Forgetting to unlock mutex after lock
  • Using non-existent methods like run or start
  • Not wrapping critical code in a block
3. What will be the output of this Ruby code?
require 'thread'
mutex = Mutex.new
counter = 0
threads = 3.times.map do
  Thread.new do
    2.times do
      mutex.synchronize { counter += 1 }
    end
  end
end
threads.each(&:join)
puts counter
medium
A. 2
B. 3
C. 6
D. 0

Solution

  1. Step 1: Count total increments

    There are 3 threads, each increments counter 2 times, total 3*2=6 increments.
  2. Step 2: Understand mutex effect

    Mutex ensures increments happen safely without losing counts, so final counter is 6.
  3. Final Answer:

    6 -> Option C
  4. Quick Check:

    3 threads * 2 increments each = 6 [OK]
Hint: Multiply threads by increments inside synchronize block [OK]
Common Mistakes:
  • Ignoring mutex and expecting wrong count
  • Counting only one thread's increments
  • Confusing thread join effect
4. Identify the problem in this Ruby code using Mutex and how to fix it:
require 'thread'
mutex = Mutex.new
counter = 0
threads = 2.times.map do
  Thread.new do
    mutex.lock
    counter += 1
  end
end
threads.each(&:join)
puts counter
medium
A. Threads are not joined properly; fix by joining inside the thread block
B. Mutex is not needed here; remove mutex.lock
C. counter should be initialized inside the thread
D. Missing mutex.unlock causes deadlock; fix by adding mutex.unlock after increment

Solution

  1. Step 1: Check mutex usage

    Mutex is locked but never unlocked, causing threads to wait forever (deadlock).
  2. Step 2: Fix by unlocking mutex

    After increment, call mutex.unlock or better use mutex.synchronize to auto-handle locking/unlocking.
  3. Final Answer:

    Missing mutex.unlock causes deadlock; fix by adding mutex.unlock after increment -> Option D
  4. Quick Check:

    Always unlock mutex after lock to avoid deadlock [OK]
Hint: Always unlock mutex or use synchronize block [OK]
Common Mistakes:
  • Forgetting to unlock mutex
  • Removing mutex when needed
  • Initializing shared data inside threads incorrectly
5. You want to safely update a shared hash from multiple threads in Ruby. Which approach correctly uses Mutex to avoid race conditions?
shared_hash = {}
mutex = Mutex.new
threads = 5.times.map do |i|
  Thread.new do
    10.times do |j|
      # What goes here?
    end
  end
end
threads.each(&:join)
puts shared_hash.size
hard
A. mutex.synchronize { shared_hash[i * 10 + j] = "value" }
B. shared_hash[i * 10 + j] = "value"
C. mutex.lock; shared_hash[i * 10 + j] = "value";
D. Thread.pass; shared_hash[i * 10 + j] = "value"

Solution

  1. Step 1: Understand thread safety for shared hash

    Multiple threads writing to shared_hash need protection to avoid data corruption.
  2. Step 2: Use mutex.synchronize for safe updates

    mutex.synchronize { shared_hash[i * 10 + j] = "value" } wraps the update inside mutex.synchronize, ensuring only one thread writes at a time.
  3. Final Answer:

    mutex.synchronize { shared_hash[i * 10 + j] = "value" } -> Option A
  4. Quick Check:

    Use synchronize block to protect shared hash updates [OK]
Hint: Wrap shared data updates inside mutex.synchronize { } [OK]
Common Mistakes:
  • Updating shared hash without mutex
  • Locking mutex without unlocking
  • Using Thread.pass instead of mutex
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard