Принятый ответ не представляет, как работает synchronize
Вы можете просто закомментировать synchronize do
и запустить скрипт принятого ответа - вывод будет таким же: 200_000
!
Итак, вот пример, показывающий разницу между работой с / без synchronize
блока:
Пример небезопасного потока:
#! /usr/bin/env ruby
require 'monitor'
class Counter < Monitor
attr_reader :count
def initialize
@count = 0
super
end
def tick i
puts "before (#{ i }): #{ @count }"
@count += 1
puts "after (#{ i }): #{ @count }"
end
end
c = Counter.new
3.times.map do |i|
Thread.new do
c.tick i
end
end.each(&:join)
puts c.count
В выводе вы получите что-то вроде этого:
before (1): 0
after (1): 1
before (2): 0
before (0): 0 <- !!
after (2): 2
after (0): 3 <- !!
Total: 3
Когда поток (0)
начинался, count
был равен 0
, но после добавления +1
его значение было 3
.
Что здесь происходит?
Когда потоки запускаются, они видят начальное значение count
. Но когда каждый из них попытается добавить +1
, значение станет другим в результате параллельного вычисления. Без правильной синхронизации частичное состояние count
непредсказуемо.
Атомарность
Теперь мы называем эти операции атомными :
#! /usr/bin/env ruby
require 'monitor'
class Counter < Monitor
attr_reader :count
def initialize
@count = 0
super
end
def tick i
synchronize do
puts "before (#{ i }): #{ @count }"
@count += 1
puts "after (#{ i }): #{ @count }"
end
end
end
c = Counter.new
3.times.map do |i|
Thread.new do
c.tick i
end
end.each(&:join)
puts c.count
Выход:
before (1): 0
after (1): 1
before (0): 1
after (0): 2
before (2): 2
after (2): 3
Total: 3
Теперь, используя блок synchronize
, мы обеспечиваем атомарность операции добавления.
но потоки все еще работают в случайном порядке (1-> 0-> 2)
Для подробного объяснения, вы можете продолжить чтение этой статьи .