Безопасность потока: переменные класса в 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
Ответы
Ответ 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