web-dev-qa-db-ja.com

rubyでスレッドセーフではないものを知る方法は?

Rails 4 から始まり、デフォルトではすべてがスレッド環境で実行する必要があります。これが意味するのは、記述するコードすべてです[〜#〜] and [〜#〜][〜#〜] all [〜#〜]使用する宝石はthreadsafeである必要があります

そのため、これに関する質問はほとんどありません。

  1. ruby/railsでスレッドセーフではないものは何ですか? VsRuby/railsのスレッドセーフとは何ですか?
  2. スレッドセーフまたはその逆として知られている宝石のリストはありますか?
  3. スレッドセーフではないコードの一般的なパターンのリスト@result ||= some_method
  4. データ構造は、Ruby Hashなどのlangコアなど)スレッドセーフですか?
  5. MRIでは、 GVL/GIL があります。これは、1つだけRubyスレッドは、IO、スレッドセーフな変更は私たちに影響しますか?
89
CuriousMind

コアデータ構造はいずれもスレッドセーフではありません。 Rubyに同梱されているのは、標準ライブラリ(_require 'thread'; q = Queue.new_)のキュー実装のみです。

MRIのGILは、スレッドの安全性の問題から私たちを救いません。 2つのスレッドがRubyコード同時にを実行できないことを確認するだけです。つまり、2つの異なるCPUでまったく同時に実行できます。スレッドは、コードのどの時点でも一時停止および再開できます。 _@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }_のようなコードを書く場合複数のスレッドから共有変数を変更すると、その後の共有変数の値は決定的ではありません。 GILは多かれ少なかれシングルコアシステムのシミュレーションであり、正しい並行プログラムを作成する際の基本的な問題は変わりません。

MRIがNode.jsのようにシングルスレッドであったとしても、並行性について考える必要があります。変数が増分された例は問題なく機能しますが、物事が非決定的な順序で発生し、1つのコールバックが別のコールバックの結果を壊す競合状態を得ることができます。シングルスレッドの非同期システムは簡単に推論できますが、同時実行性の問題はありません。複数のユーザーがいるアプリケーションを考えてみてください:2人のユーザーが多少ともStack Overflowの投稿で編集をヒットした場合、投稿を編集してから保存をクリックします。同じ投稿を読みますか?

Rubyでは、他のほとんどの同時実行時と同様に、複数の操作であるものはすべてスレッドセーフではありません。 _@n += 1_は複数の操作であるため、スレッドセーフではありません。 _@n = 1_は1つの操作であるためスレッドセーフです(内部では多くの操作が行われるため、「スレッドセーフ」である理由を詳細に説明しようとすると、おそらく問題が発生しますが、最終的には割り当てから一貫性のない結果を得る)。 _@n ||= 1_はそうではなく、他の短縮操作+割り当てもありません。私が何度も犯した間違いの1つは_return unless @started; @started = true_を書くことです。これはまったくスレッドセーフではありません。

Rubyのスレッドセーフおよびスレッドセーフでないステートメントの正式なリストは知りませんが、簡単な経験則があります:式が1つの(副作用のない)操作のみを行う場合、おそらくスレッドセーフです。例:_a + b_は大丈夫、_a = b_も大丈夫、a.foo(b)は大丈夫、メソッドfooが副作用なしの場合(Rubyのほぼすべてがメソッド呼び出しであるため、多くの場合、割り当てでさえ、これは他の例にも当てはまります)。このコンテキストでの副作用とは、状態が変化することを意味します。 def foo(x); @x = x; endnot副作用なしです。

Rubyでスレッドセーフコードを記述することで最も難しいことの1つは、配列、ハッシュ、文字列を含むすべてのコアデータ構造が可変であることです。誤ってあなたの状態の一部を漏らすことは非常に簡単です、そして、その部分が可変であるとき、ものは本当にめちゃくちゃになることができます。次のコードを検討してください。

_class Thing
  attr_reader :stuff

  def initialize(initial_stuff)
    @stuff = initial_stuff
    @state_lock = Mutex.new
  end

  def add(item)
    @state_lock.synchronize do
      @stuff << item
    end
  end
end
_

このクラスのインスタンスはスレッド間で共有でき、スレッドに安全に追加できますが、並行性のバグがあります(唯一ではありません)。オブジェクトの内部状態はstuffアクセサーを通じてリークします。カプセル化の観点から問題があるだけでなく、並行性ワームの缶を開きます。たぶん誰かがその配列を取り、それを他の場所に渡し、そのコードは今度はその配列を所有し、それで何でもやりたいことができると考えます。

もう1つの古典的なRubyの例は次のとおりです。

_STANDARD_OPTIONS = {:color => 'red', :count => 10}

def find_stuff
  @some_service.load_things('stuff', STANDARD_OPTIONS)
end
_

_find_stuff_は、最初に使用されたときは正常に動作しますが、2回目には他の何かを返します。どうして? _load_things_メソッドは、渡されたオプションハッシュを所有していると見なし、color = options.delete(:color)を実行します。これで、_STANDARD_OPTIONS_定数は同じ値を持たなくなりました。定数は、参照するものが定数であり、参照するデータ構造の不変性を保証するものではありません。このコードを同時に実行するとどうなるかを考えてください。

共有の可変状態(たとえば、複数のスレッドがアクセスするオブジェクトのインスタンス変数、複数のスレッドがアクセスするハッシュや配列などのデータ構造)を回避する場合、スレッドの安全性はそれほど難しくありません。同時にアクセスされるアプリケーションの部分を最小限に抑え、そこで作業に集中するようにしてください。 IIRCは、Railsアプリケーションで、すべてのリクエストに対して新しいコントローラーオブジェクトが作成されるため、単一のスレッドでのみ使用され、コントローラーから作成するモデルオブジェクトについても同じことが言えます。ただし、Railsはグローバル変数の使用も推奨します(User.find(...)はグローバル変数Userを使用します。これはクラスとしてのみ考えることができ、クラスです、ただしグローバル変数の名前空間でもあります)、これらの一部は読み取り専用であるため安全ですが、便利なためこれらのグローバル変数に保存することもあります。グローバルにアクセス可能なものを使用する場合は、十分に注意してください。

かなり以前からスレッド環境でRailsを実行することが可能でしたので、Railsの専門家でなくても、心配する必要はないと言います。 Rails自体に関するスレッドセーフについて。上記のことをいくつか行うことで、スレッドセーフではないRailsアプリケーションを作成できます。他のgemは、そうでないと言わない限りスレッドセーフではないと仮定し、そうでないと仮定すると、コードを調べます(ただし、_@n ||= 1_は、スレッドセーフではないという意味ではありません。適切なコンテキストで行うのは完全に正当なことです。代わりに、グローバル変数の可変状態、メソッドに渡される可変オブジェクトの処理方法などを探す必要があります。特にオプションハッシュの処理方法)。

最後に、スレッドが安全でないことは推移的な特性です。スレッドセーフでないものを使用するものはすべて、スレッドセーフではありません。

104
Theo

Theoの答えに加えて、Rails!

  • クラス変数

    @@i_exist_across_threads

  • [〜#〜] env [〜#〜]

    ENV['DONT_CHANGE_ME']

  • スレッド

    Thread.start

10
crizCraig

Rails 4から始まり、すべてがデフォルトでスレッド環境で実行する必要があります

これは完全に正しいわけではありません。スレッドセーフRailsはデフォルトでオンになっています。Passenger(コミュニティ)やUnicornなどのマルチプロセスアプリサーバーに展開する場合、違いはまったくありません。この変更は、 PumaまたはPassenger Enterprise> 4.0などのマルチスレッド環境に展開する場合

以前は、マルチスレッドアプリサーバーにデプロイする場合、config.threadsafeを有効にする必要がありました。効果がなかったか、またはRails単一プロセスで実行中のアプリ( Prooflink ))にも適用されました。

ただし、すべてのRails 4 ストリーミング の利点とマルチスレッド展開のその他のリアルタイム機能が必要な場合は、 this @Theo悲しいことに、Railsアプリの場合、実際にはリクエスト中に静的状態の変更を省略する必要があります。これは従うべき簡単な方法ですが、残念ながら、私が覚えている限りでは、JRubyプロジェクトのCharles Oliver Nutterが this podcastでいくつかのヒントを持っていました。

また、複数のスレッドがアクセスするいくつかのデータ構造が必要な場合、純粋なコンカレントRubyプログラミングを作成したい場合は、 thread_safe gem有用。

8
dre-hh