Thread synchronization with Mutex in Ruby - Time & Space Complexity
When using threads with a mutex, we want to see how the program's speed changes as more work is done.
We ask: How does locking and unlocking affect the time as the input grows?
Analyze the time complexity of the following code snippet.
require 'thread'
mutex = Mutex.new
counter = 0
threads = 10.times.map do
Thread.new do
1000.times do
mutex.synchronize do
counter += 1
end
end
end
end
threads.each(&:join)
This code creates 10 threads, each adding 1 to a shared counter 1000 times, using a mutex to avoid conflicts.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The inner loop runs 1000 times per thread, each time locking and unlocking the mutex to update the counter.
- How many times: 10 threads x 1000 times = 10,000 total synchronized increments.
As the number of threads or increments grows, the total locking operations grow proportionally.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 threads x 1000 increments | 10,000 mutex locks/unlocks |
| 10 threads x 10,000 increments | 100,000 mutex locks/unlocks |
| 100 threads x 10,000 increments | 1,000,000 mutex locks/unlocks |
Pattern observation: The total operations grow directly with the number of threads and increments combined.
Time Complexity: O(n)
This means the time grows in a straight line with the total number of increments across all threads.
[X] Wrong: "Using a mutex makes the program run in constant time regardless of input size."
[OK] Correct: Each lock and unlock still takes time, so as the number of increments grows, the total time grows too.
Understanding how mutex locking affects time helps you write safe and efficient multi-threaded programs, a useful skill in many real projects.
"What if we replaced the mutex with a lock-free atomic operation? How would the time complexity change?"
Practice
Mutex in Ruby threads?Solution
Step 1: Understand what shared data means in threads
Shared data is information that multiple threads can access or change.Step 2: Identify the role of Mutex
Mutex ensures only one thread changes shared data at a time to avoid errors.Final Answer:
To prevent multiple threads from accessing shared data at the same time -> Option BQuick Check:
Mutex protects shared data = prevent simultaneous access [OK]
- Thinking Mutex speeds up threads
- Believing Mutex creates threads
- Assuming Mutex stops threads completely
Solution
Step 1: Recall Mutex usage in Ruby
Ruby's Mutex class provides asynchronizemethod to wrap critical code safely.Step 2: Compare options
Onlymutex.synchronize { critical_code }is the correct and safe syntax.Final Answer:
mutex.synchronize { critical_code } -> Option AQuick Check:
Use synchronize method to protect code [OK]
- Forgetting to unlock mutex after lock
- Using non-existent methods like run or start
- Not wrapping critical code in a block
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 counterSolution
Step 1: Count total increments
There are 3 threads, each increments counter 2 times, total 3*2=6 increments.Step 2: Understand mutex effect
Mutex ensures increments happen safely without losing counts, so final counter is 6.Final Answer:
6 -> Option CQuick Check:
3 threads * 2 increments each = 6 [OK]
- Ignoring mutex and expecting wrong count
- Counting only one thread's increments
- Confusing thread join effect
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 counterSolution
Step 1: Check mutex usage
Mutex is locked but never unlocked, causing threads to wait forever (deadlock).Step 2: Fix by unlocking mutex
After increment, callmutex.unlockor better usemutex.synchronizeto auto-handle locking/unlocking.Final Answer:
Missing mutex.unlock causes deadlock; fix by adding mutex.unlock after increment -> Option DQuick Check:
Always unlock mutex after lock to avoid deadlock [OK]
- Forgetting to unlock mutex
- Removing mutex when needed
- Initializing shared data inside threads incorrectly
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.sizeSolution
Step 1: Understand thread safety for shared hash
Multiple threads writing to shared_hash need protection to avoid data corruption.Step 2: Use mutex.synchronize for safe updates
mutex.synchronize { shared_hash[i * 10 + j] = "value" } wraps the update insidemutex.synchronize, ensuring only one thread writes at a time.Final Answer:
mutex.synchronize { shared_hash[i * 10 + j] = "value" } -> Option AQuick Check:
Use synchronize block to protect shared hash updates [OK]
- Updating shared hash without mutex
- Locking mutex without unlocking
- Using Thread.pass instead of mutex
Ready to prove it?
Pass the test and this pattern moves unknown
