在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
答案 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上,图片发生了变化。对同一算法的多次执行产生了值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
答案 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上运行。