Concurrency and Modules
Mutex Inventory
A mutex keeps shared state updates consistent when more than one thread changes the same object.
mutex updates
Wrap the read-modify-write section with `synchronize` so one thread updates the inventory at a time.
Mutex Inventory
mutex_inventory.rb
Replay: real traced execution (multi-file project)
orders_per_worker = 2
stock = { "book" => 10 }
lock = Mutex.new
workers = 2.times.map do
Thread.new do
orders_per_worker.times do
lock.synchronize do
current = stock["book"]
stock["book"] = current - 1
end
end
end
end
workers.each(&:join)
sold = 10 - stock["book"]
puts "orders_per_worker=#{orders_per_worker}"
puts "sold=#{sold}"
puts "remaining=#{stock["book"]}"
orders_per_worker = 1
stock = { "book" => 10 }
lock = Mutex.new
workers = 2.times.map do
Thread.new do
orders_per_worker.times do
lock.synchronize do
current = stock["book"]
stock["book"] = current - 1
end
end
end
end
workers.each(&:join)
sold = 10 - stock["book"]
puts "orders_per_worker=#{orders_per_worker}"
puts "sold=#{sold}"
puts "remaining=#{stock["book"]}"
orders_per_worker = 3
stock = { "book" => 10 }
lock = Mutex.new
workers = 2.times.map do
Thread.new do
orders_per_worker.times do
lock.synchronize do
current = stock["book"]
stock["book"] = current - 1
end
end
end
end
workers.each(&:join)
sold = 10 - stock["book"]
puts "orders_per_worker=#{orders_per_worker}"
puts "sold=#{sold}"
puts "remaining=#{stock["book"]}"
orders_per_worker ← 2, stock ← {"book"=>10}, lock ← ⟨Thread::Mutex A⟩
1orders_per_worker→ 2 = 2 #@orders_per_worker=1, 32stock→ {"book"=>10} = { "book" => 10 }3lock→ ⟨Thread::Mutex A⟩ = Mutex.new45workers = 2.times.map do6 Thread.new do7 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14endworkers ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]
pass 2 of 25workers→ [⟨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 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker2.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 46Thread.new do7 orders_per_worker.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13endAll 4 passes — pass 1 is the card above pass orders_per_worker1 — 2 2 3 — 4 — current ← 10
pass 1 of 47orders_per_worker.times do8 lock.synchronize do9 current→ 10 = stock["book"]1010 stock["book"] = current10 - 111 endAll 4 passes — pass 1 is the card above pass stock["book"]orders_per_workercurrent1 10 — 10 2 9 2 9 3 8 — 8 4 7 — 7 do
pass 2 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker2.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14endsold ← 4
16workers.each(&:join)[⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 dead⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 dead⟩]1718sold→ 4 = 10 - stock["book"]619puts "orders_per_worker=#{orders_per_worker2}"20puts "sold=#{sold4}"21puts "remaining=#{stock["book"]6}"outputorders_per_worker=2 sold=4 remaining=6
orders_per_worker ← 1, stock ← {"book"=>10}, lock ← ⟨Thread::Mutex A⟩
1orders_per_worker→ 1 = 12stock→ {"book"=>10} = { "book" => 10 }3lock→ ⟨Thread::Mutex A⟩ = Mutex.new45workers = 2.times.map do6 Thread.new do7 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14endworkers ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]
pass 2 of 25workers→ [⟨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 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker1.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 26Thread.new do7 orders_per_worker.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13endcurrent ← 10
pass 1 of 27orders_per_worker.times do8 lock.synchronize do9 current→ 10 = stock["book"]1010 stock["book"] = current10 - 111 enddo
pass 2 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker1.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 2 of 26Thread.new do7 orders_per_worker.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13endcurrent ← 9
pass 2 of 27orders_per_worker.times do8 lock.synchronize do9 current→ 9 = stock["book"]910 stock["book"] = current9 - 111 endsold ← 2
16workers.each(&:join)[⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 dead⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 dead⟩]1718sold→ 2 = 10 - stock["book"]819puts "orders_per_worker=#{orders_per_worker1}"20puts "sold=#{sold2}"21puts "remaining=#{stock["book"]8}"outputorders_per_worker=1 sold=2 remaining=8
orders_per_worker ← 3, stock ← {"book"=>10}, lock ← ⟨Thread::Mutex A⟩
1orders_per_worker→ 3 = 32stock→ {"book"=>10} = { "book" => 10 }3lock→ ⟨Thread::Mutex A⟩ = Mutex.new45workers = 2.times.map do6 Thread.new do7 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14endworkers ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]
pass 2 of 25workers→ [⟨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 orders_per_worker.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker3.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14enddo
pass 1 of 66Thread.new do7 orders_per_worker.times do8 lock⟨Thread::Mutex A⟩.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13endAll 6 passes — pass 1 is the card above pass orders_per_worker1 — 2 — 3 3 4 — 5 — 6 — current ← 10
pass 1 of 67orders_per_worker.times do8 lock.synchronize do9 current→ 10 = stock["book"]1010 stock["book"] = current10 - 111 endAll 6 passes — pass 1 is the card above pass stock["book"]orders_per_workercurrent1 10 — 10 2 9 — 9 3 8 3 8 4 7 — 7 5 6 — 6 6 5 — 5 do
pass 2 of 25workers = 2.times.map do6 Thread.new do7 orders_per_worker3.times do8 lock.synchronize do9 current = stock["book"]10 stock["book"] = current - 111 end12 end13 end14endsold ← 6
16workers.each(&:join)[⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 dead⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 dead⟩]1718sold→ 6 = 10 - stock["book"]419puts "orders_per_worker=#{orders_per_worker3}"20puts "sold=#{sold6}"21puts "remaining=#{stock["book"]4}"outputorders_per_worker=3 sold=6 remaining=4