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

orders_per_worker
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"]}"
  1. 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  end14end
  2. do

    pass 1 of 2
    5workers = 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  end14end
  3. workers ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]

    pass 2 of 2
    5workers→ [⟨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  end14end
  4. do

    pass 1 of 2
    5workers = 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  end14end
  5. do

    pass 1 of 4
    6Thread.new do7  orders_per_worker.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      current = stock["book"]10      stock["book"] = current - 111    end12  end13end
    All 4 passes — pass 1 is the card above
    passorders_per_worker
    1
    22
    3
    4
  6. current ← 10

    pass 1 of 4
    7orders_per_worker.times do8  lock.synchronize do9    current→ 10 = stock["book"]1010    stock["book"] = current10 - 111  end
    All 4 passes — pass 1 is the card above
    passstock["book"]orders_per_workercurrent
    11010
    2929
    388
    477
  7. do

    pass 2 of 2
    5workers = 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  end14end
  8. sold ← 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
  1. 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  end14end
  2. do

    pass 1 of 2
    5workers = 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  end14end
  3. workers ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]

    pass 2 of 2
    5workers→ [⟨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  end14end
  4. do

    pass 1 of 2
    5workers = 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  end14end
  5. do

    pass 1 of 2
    6Thread.new do7  orders_per_worker.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      current = stock["book"]10      stock["book"] = current - 111    end12  end13end
  6. current ← 10

    pass 1 of 2
    7orders_per_worker.times do8  lock.synchronize do9    current→ 10 = stock["book"]1010    stock["book"] = current10 - 111  end
  7. do

    pass 2 of 2
    5workers = 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  end14end
  8. do

    pass 2 of 2
    6Thread.new do7  orders_per_worker.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      current = stock["book"]10      stock["book"] = current - 111    end12  end13end
  9. current ← 9

    pass 2 of 2
    7orders_per_worker.times do8  lock.synchronize do9    current→ 9 = stock["book"]910    stock["book"] = current9 - 111  end
  10. sold ← 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
  1. 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  end14end
  2. do

    pass 1 of 2
    5workers = 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  end14end
  3. workers ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]

    pass 2 of 2
    5workers→ [⟨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  end14end
  4. do

    pass 1 of 2
    5workers = 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  end14end
  5. do

    pass 1 of 6
    6Thread.new do7  orders_per_worker.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      current = stock["book"]10      stock["book"] = current - 111    end12  end13end
    All 6 passes — pass 1 is the card above
    passorders_per_worker
    1
    2
    33
    4
    5
    6
  6. current ← 10

    pass 1 of 6
    7orders_per_worker.times do8  lock.synchronize do9    current→ 10 = stock["book"]1010    stock["book"] = current10 - 111  end
    All 6 passes — pass 1 is the card above
    passstock["book"]orders_per_workercurrent
    11010
    299
    3838
    477
    566
    655
  7. do

    pass 2 of 2
    5workers = 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  end14end
  8. sold ← 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