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

rounds
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}"
  1. 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  end13end
  2. do

    pass 1 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  3. threads ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]

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

    pass 1 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds3.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  5. do

    pass 1 of 6
    6Thread.new do7  rounds.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      counter += 110    end11  end12end
    All 6 passes — pass 1 is the card above
    passrounds
    1
    2
    33
    4
    5
    6
  6. do

    pass 1 of 6
    7rounds.times do8  lock.synchronize do9    counter0 += 110  end
    All 6 passes — pass 1 is the card above
    passcounterrounds
    10
    21
    323
    43
    54
    65
  7. do

    pass 2 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds3.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  8. expected ← 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
  1. 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  end13end
  2. do

    pass 1 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  3. threads ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]

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

    pass 1 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds2.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  5. do

    pass 1 of 4
    6Thread.new do7  rounds.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      counter += 110    end11  end12end
    All 4 passes — pass 1 is the card above
    passrounds
    1
    22
    3
    4
  6. do

    pass 1 of 4
    7rounds.times do8  lock.synchronize do9    counter0 += 110  end
    All 4 passes — pass 1 is the card above
    passcounterrounds
    10
    212
    32
    43
  7. do

    pass 2 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds2.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  8. expected ← 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
  1. 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  end13end
  2. do

    pass 1 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  3. threads ← [⟨Thread B /tmp/execution/⟨tmp D⟩.rb:57 run⟩, ⟨Thread C /tmp/execution/⟨tmp D⟩.rb:57 run⟩]

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

    pass 1 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds5.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  5. do

    pass 1 of 10
    6Thread.new do7  rounds.times do8    lock⟨Thread::Mutex A⟩.synchronize do9      counter += 110    end11  end12end
    All 10 passes — pass 1 is the card above
    passrounds
    1
    2
    3
    4
    55
    6
    7
    8
    9
    10
  6. do

    pass 1 of 10
    7rounds.times do8  lock.synchronize do9    counter0 += 110  end
    All 10 passes — pass 1 is the card above
    passcounterrounds
    10
    21
    32
    43
    545
    65
    76
    87
    98
    109
  7. do

    pass 2 of 2
    5threads = 2.times.map do6  Thread.new do7    rounds5.times do8      lock.synchronize do9        counter += 110      end11    end12  end13end
  8. expected ← 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