Потокобезопасность: переменные класса в Ruby - PullRequest
36 голосов
/ 04 марта 2012

Выполнение записи / чтения переменных класса в 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: ПЕРЕМЕННОЕ ХРАНИЛИЩЕ НА МЕТАКЛАСЕ

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

Ответы [ 3 ]

22 голосов
/ 05 марта 2012

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

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


Безопасность потоков, присущая языку Ruby, зависит от реализации.

MRI до 1.9, реализованные потоки на виртуальной машинеуровень .Это означает, что, хотя 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
9 голосов
/ 21 ноября 2017

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

Примеры 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 ответ на вопрос об этом

7 голосов
/ 16 мая 2016

Примеры 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.

...