Подтвердить что ты не робот

Безопасность потока: переменные класса в Ruby

Выполнение записи/чтения переменных класса в Ruby не является потокобезопасным. Выполнение операций записи/чтения в переменных экземпляра представляется потокобезопасным. Тем не менее, безопасно ли потоковое выполнение записи/чтения переменных экземпляра объекта класса или метакласса?

Каковы различия между этими тремя (надуманными) примерами с точки зрения безопасности потоков?

ПРИМЕР 1: ВЗАИМНОЕ ИСКЛЮЧЕНИЕ

class BestUser # (singleton class)
  @@instance_lock = Mutex.new

  # Memoize instance
  def self.instance
    @@instance_lock.synchronize do
      @@instance ||= best
    end
  end
end

ПРИМЕР 2: ИНСТРУКЦИЯ ПЕРЕМЕННОГО ХРАНЕНИЯ

class BestUser # (singleton class)
  # Memoize instance
  def self.instance
    @instance ||= best
  end
end

ПРИМЕР 3: ИНСТАНЦИЯ ПЕРЕМЕННОГО ХРАНЕНИЯ НА METACLASS

class BestUser # (singleton class)
  # Memoize instance
  class << self
    def instance
      @instance ||= best
    end
  end
end
4b9b3361

Ответ 1

Примеры 2 и 3 точно совпадают. Модули и классы также являются объектами, и определение метода singleton для объекта фактически определяет его в его одноэлементном классе.

С учетом сказанного, и поскольку вы уже установили доступ к переменной экземпляра, поточно-безопасный, примеры 2 и 3 являются потокобезопасными. Пример 1 также должен быть потокобезопасным, но он уступает другим двум, поскольку он требует ручной синхронизации переменных.

Однако, если вам нужно воспользоваться тем фактом, что переменные класса совместно используются в дереве наследования, вам может потребоваться использовать первый подход.


Собственная безопасность потоков языка Ruby зависит от реализации.

MRI, до 1.9, реализовал потоки на уровне VM. Это означает, что хотя Ruby способен планировать выполнение кода, ничто не работает параллельно в рамках одного процесса Ruby. Ruby 1.9 использует собственные потоки, синхронизированные с блокировкой глобального интерпретатора. Только контекст, который содержит блокировку, может выполнять код.

n, x = 10, 0

n.times do
  Thread.new do
    n.times do
      x += 1
    end
  end
end

sleep 1
puts x
# 100

Значение x всегда согласовано на MRI. Однако на JRuby картина меняется. Несколько выполнений одного и того же алгоритма дали значения 76, 87, 98, 88, 94. Результатом может быть все, потому что JRuby использует потоки Java, которые являются настоящими потоками и выполняются параллельно.

Как и на языке Java, для безопасной работы потоков в JRuby требуется ручная синхронизация. Следующий код всегда приводит к согласованным значениям для x:

require 'thread'
n, x, mutex = 10, 0, Mutex.new

n.times do
  Thread.new do
    n.times do
      mutex.synchronize do
        x += 1
      end
    end
  end
end

sleep 1
puts x
# 100

Ответ 2

Примеры 2 и 3 точно совпадают. Они не являются безопасными для потока.

См. пример ниже.

class Foo
  def self.bar
    @bar ||= create_no
  end

  def self.create_no
    no = rand(10000)
    sleep 1
    no
  end
end

10.times.map do
  Thread.new do
    puts "bar is #{Foo.bar}"
  end
end.each(&:join)

Результат не такой. Результат аналогичен при использовании мьютекса, как показано ниже.

class Foo
  @mutex = Mutex.new

  def self.bar
    @mutex.synchronize {
      @bar ||= create_no
    }
  end

  def self.create_no
    no = rand(10000)
    sleep 1
    no
  end
end

10.times.map do
  Thread.new do
    puts "bar is #{Foo.bar}"
  end
end.each(&:join)

Он запускается на CRuby 2.3.0.

Ответ 3

Переменные экземпляра не являются потокобезопасными (и переменные класса еще менее безопасны для потоков)

Пример 2 и 3, оба с переменными экземпляра, эквивалентны, и они НЕ потокобезопасны, как указано в @VincentXie. Однако, вот лучший пример, чтобы показать, почему они не являются:

class Foo
  def self.bar(message)
    @bar ||= message
  end
end

t1 = Thread.new do
    puts "bar is #{Foo.bar('thread1')}"
end

t2 = Thread.new do
    puts "bar is #{Foo.bar('thread2')}"
end

sleep 2

t1.join
t2.join

=> bar is thread1
=> bar is thread1

Поскольку переменная экземпляра является общей для всех потоков, например, @VincentXie, указанная в его комментарии.

PS: переменные экземпляра иногда упоминаются как "переменные экземпляра класса", в зависимости от контекста, в котором они используются:

Когда self является классом, они являются переменными экземпляра классов (класс переменные экземпляра). Когда self является объектом, они являются экземпляром переменные объектов (переменные экземпляра). - Ответ на вопрос о WindorC