Concurrency Basics
Mutex Counter
A mutex protects shared state while multiple threads update it.
mutex
`Mutex#synchronize` lets one thread at a time run the critical section that changes shared data.
Mutex Counter
mutex_counter.rb
Replay: real traced execution (multi-file project)
rounds = 3
counter = 0
lock = Mutex.new
threads = 2.times.map do
Thread.new do
rounds.times do
lock.synchronize do
counter += 1
end
end
end
end
threads.each(&:join)
expected = rounds * threads.length
puts "rounds=#{rounds}"
puts "counter=#{counter}"
puts "expected=#{expected}"
rounds = 2
counter = 0
lock = Mutex.new
threads = 2.times.map do
Thread.new do
rounds.times do
lock.synchronize do
counter += 1
end
end
end
end
threads.each(&:join)
expected = rounds * threads.length
puts "rounds=#{rounds}"
puts "counter=#{counter}"
puts "expected=#{expected}"
rounds = 5
counter = 0
lock = Mutex.new
threads = 2.times.map do
Thread.new do
rounds.times do
lock.synchronize do
counter += 1
end
end
end
end
threads.each(&:join)
expected = rounds * threads.length
puts "rounds=#{rounds}"
puts "counter=#{counter}"
puts "expected=#{expected}"
rounds ← 3, counter ← 0, lock ← ⟨Thread::Mutex A⟩
1rounds→ 3 = 3 #@rounds=2, 52counter→ 0 = 03lock→ ⟨Thread::Mutex A⟩ = Mutex.new45threads = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 25threads = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13endthreads ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]
pass 2 of 25threads→ [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩] = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 25threads = 2.times.map do6 Thread.new do7 rounds3.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 66Thread.new do7 rounds.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 counter += 110 end11 end12endAll 6 passes — pass 1 is the card above pass rounds1 — 2 — 3 3 4 — 5 — 6 — do
pass 1 of 67rounds.times do8 lock.synchronize do9 counter0 += 110 endAll 6 passes — pass 1 is the card above pass counterrounds1 0 — 2 1 — 3 2 3 4 3 — 5 4 — 6 5 — do
pass 2 of 25threads = 2.times.map do6 Thread.new do7 rounds3.times do8 lock.synchronize do9 counter += 110 end11 end12 end13endexpected ← 6
15threads.each(&:join)[⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 dead⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 dead⟩]16expected→ 6 = rounds3 * threads.length21718puts "rounds=#{rounds3}"19puts "counter=#{counter6}"20puts "expected=#{expected6}"outputrounds=3 counter=6 expected=6
rounds ← 2, counter ← 0, lock ← ⟨Thread::Mutex A⟩
1rounds→ 2 = 22counter→ 0 = 03lock→ ⟨Thread::Mutex A⟩ = Mutex.new45threads = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 25threads = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13endthreads ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]
pass 2 of 25threads→ [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩] = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 25threads = 2.times.map do6 Thread.new do7 rounds2.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 46Thread.new do7 rounds.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 counter += 110 end11 end12endAll 4 passes — pass 1 is the card above pass rounds1 — 2 2 3 — 4 — do
pass 1 of 47rounds.times do8 lock.synchronize do9 counter0 += 110 endAll 4 passes — pass 1 is the card above pass counterrounds1 0 — 2 1 2 3 2 — 4 3 — do
pass 2 of 25threads = 2.times.map do6 Thread.new do7 rounds2.times do8 lock.synchronize do9 counter += 110 end11 end12 end13endexpected ← 4
15threads.each(&:join)[⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 dead⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 dead⟩]16expected→ 4 = rounds2 * threads.length21718puts "rounds=#{rounds2}"19puts "counter=#{counter4}"20puts "expected=#{expected4}"outputrounds=2 counter=4 expected=4
rounds ← 5, counter ← 0, lock ← ⟨Thread::Mutex A⟩
1rounds→ 5 = 52counter→ 0 = 03lock→ ⟨Thread::Mutex A⟩ = Mutex.new45threads = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 25threads = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13endthreads ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]
pass 2 of 25threads→ [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩] = 2.times.map do6 Thread.new do7 rounds.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 25threads = 2.times.map do6 Thread.new do7 rounds5.times do8 lock.synchronize do9 counter += 110 end11 end12 end13enddo
pass 1 of 106Thread.new do7 rounds.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 counter += 110 end11 end12endAll 10 passes — pass 1 is the card above pass rounds1 — 2 — 3 — 4 — 5 5 6 — 7 — 8 — 9 — 10 — do
pass 1 of 107rounds.times do8 lock.synchronize do9 counter0 += 110 endAll 10 passes — pass 1 is the card above pass counterrounds1 0 — 2 1 — 3 2 — 4 3 — 5 4 5 6 5 — 7 6 — 8 7 — 9 8 — 10 9 — do
pass 2 of 25threads = 2.times.map do6 Thread.new do7 rounds5.times do8 lock.synchronize do9 counter += 110 end11 end12 end13endexpected ← 10
15threads.each(&:join)[⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 dead⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 dead⟩]16expected→ 10 = rounds5 * threads.length21718puts "rounds=#{rounds5}"19puts "counter=#{counter10}"20puts "expected=#{expected10}"outputrounds=5 counter=10 expected=10