Rubyのクラス変数で書き込み/読み取りを実行することはスレッドセーフではありません。インスタンス変数で書き込み/読み取りを実行することはスレッドセーフであるように見えます。つまり、インスタンスで書き込み/読み取りを実行することはスレッドセーフです。クラスまたはメタクラスオブジェクトの変数?
スレッドセーフティの点で、これら3つの(考案された)例の違いは何ですか?
例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
インスタンス変数はスレッドセーフではありません(そしてクラス変数はスレッドセーフではありません)
例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の質問への回答
例2と3はまったく同じです。モジュールとクラスもオブジェクトであり、オブジェクトにシングルトンメソッドを定義すると、実際にはそのシングルトンクラスに定義されます。
つまり、インスタンス変数へのアクセスはスレッドセーフであることが既に確立されているため、例2と3はスレッドセーフです。例1もスレッドセーフである必要がありますが、手動で変数を同期する必要があるため、他の2つよりも劣っています。
ただし、継承ツリー内でクラス変数が共有されているという事実を利用する必要がある場合は、最初の方法を使用する必要があります。
Ruby言語に固有のスレッドセーフティは実装に依存します。
MRIは1.9より前にスレッドを実装しました VM level で。これは、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
の値はalwaysMRIで一貫しています。ただし、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と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)
結果は同じではありません。以下のようにmutexを使用した場合の結果は同じです。
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で実行されます。