28

在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:INSTANCE变量存储

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

实施例3:INSTANCE变量存储ON元类

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

回答

21

实施例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上,图片发生了变化。相同算法的多次执行产生了值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

如果访问实例变量实际上是线程安全的,或者仅仅基于我认为它*看起来是*的假设,您知道副手吗? – 2012-03-05 14:27:28

+0

@AnomalousThought,查看有关线程安全性的一些信息的更新答案。 – 2012-03-05 17:24:07

+0

@MatheusMoreira当你有机会时,你介意看看http://stackoverflow.com/questions/21735401/using-class-instance-variable-for-mutex-in-ruby吗?谢谢。 – 2014-02-12 20:49:37

5

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

+0

我不知道我明白。当然,线程安全的结果总是不同的,因为每个线程都可以为'@ bar'设置自己的值。如果你用@@ bar替换'@ bar',你总会得到相同的结果。基于这个假设,你是说'@@ bar'是线程安全的吗? – Magnuss 2017-03-28 06:55:38

+1

@bar是Foo类的实例变量。它不是每个线程所拥有的。它由所有线程共享。 – 2017-05-12 06:15:44

+1

实际上,线程安全意味着最顶层的例子中的结果应该不同(即其他线程不会插入实例变量),就像@Magnuss所指出的那样。所以你的例子似乎证明了OP的问题中的例子2和3是线程安全的。 – Magne 2017-11-21 13:10:29

2

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:实例变量有时称为“类的实例变量”,这取决于在其中使用它们的上下文:

当自被一个类,它们是类的实例变量(类 实例变量)。当自己是一个对象时,它们是对象变量(实例变量)的实例 。 - WindorC's answer to a question about this