线程安全:Ruby中的类变量

Jos*_*ber 39 ruby multithreading metaprogramming thread-safety 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
Run Code Online (Sandbox Code Playgroud)

例2: 实例变量存储

class BestUser # (singleton class)
  # Memoize instance
  def self.instance
    @instance ||= best
  end
end
Run Code Online (Sandbox Code Playgroud)

例3: 在METACLASS上安装可变存储器

class BestUser # (singleton class)
  # Memoize instance
  class << self
    def instance
      @instance ||= best
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

Mat*_*ira 20

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

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

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


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

在1.9之前的MRI,在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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • 如果访问实例变量实际上是线程安全的,或者仅仅基于我的假设它**出现*,你知道吗? (3认同)
  • `因为你已经建立了实例变量访问是线程安全的 - 是的,我不认为这实际上是建立或安全的假设. (3认同)
  • > Ruby语言的固有线程安全性取决于实现.事实并非如此.类实例变量是线程安全的假设也是不真实的.拥有GIL不会使您的代码线程安全; 它确保您的代码不会同时运行.这消除了线程安全的危险,但不会使代码本身线程安全.没有理由只是因为你在MRI上编写非线程安全代码,特别是如果你计划有一天移植到RBX或j/cruby. (2认同)

Mag*_*gne 11

实例变量不是线程安全的(并且类变量的线程安全性更低)

示例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
Run Code Online (Sandbox Code Playgroud)

因为实例变量在所有线程中共享,例如@VincentXie在他的评论中说明.

PS:实例变量有时被称为"类实例变量",具体取决于它们的使用环境:

当self是一个类时,它们是类的实例变量(类实例变量).当self是一个对象时,它们是对象的实例变量(实例变量).- WindorC对此问题的回答


Vin*_*Xie 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)
Run Code Online (Sandbox Code Playgroud)

结果不一样.使用下面的互斥锁时结果相同.

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)
Run Code Online (Sandbox Code Playgroud)

它在CRuby 2.3.0上运行.

  • @bar 是 Foo 类的实例变量。它不属于每个线程。它由所有线程共享。 (2认同)