线程安全:Ruby中的类变量

时间:2012-03-04 19:41:58

标签: ruby multithreading thread-safety metaprogramming ruby-1.9.2

在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: INSTANCE VARIABLE STORAGE

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

3 个答案:

答案 0 :(得分:22)

示例2和3完全相同。模块和类也是对象,在对象上定义单例方法实际上是在其单例类上定义它。

话虽如此,并且由于您已经建立了实例变量访问是线程安全的,示例2和3是线程安全的。示例1也应该是线程安全的,但它不如其他两个,因为它需要手动变量同步。

但是,如果您需要利用继承树中共享类变量的事实,则可能必须使用第一种方法。


Ruby语言的固有线程安全性取决于实现。

MRI,在1.9之前,实现了线程at the VM level。这意味着即使Ruby能够安排代码执行,但在单个Ruby进程中并没有真正运行并行。 Ruby 1.9使用与global interpreter lock同步的本机线程。只有持有锁的上下文才可以执行代码。

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上,图片发生了变化。对同一算法的多次执行产生了值7687988894。结果可能是任何因为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

答案 1 :(得分:8)

Instance variables are not thread safe (类变量的线程安全性更低)

示例2和3,都是实例变量,是等效的,它们是 NOT 线程安全,如@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's answer to a question about this

答案 2 :(得分:7)

示例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上运行。