web-dev-qa-db-ja.com

スレッドセーフティ:Rubyのクラス変数

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
41

インスタンス変数はスレッドセーフではありません(そしてクラス変数はスレッドセーフではありません)

例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の質問への回答

12
Magne

例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では状況が異なります。同じアルゴリズムを複数回実行すると、値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
20
Matheus Moreira

例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で実行されます。

7
Vincent Xie