95

Rails 4以降では、デフォルトですべてをスレッド環境で実行する必要があります。これが意味することは、私たちが書く すべてのコードと、私たちが使用するすべての宝石は、threadsafe

だから、私はこれについていくつか質問があります:

  1. Ruby/Railsでスレッドセーフではないものは何ですか? vs ruby​​/railsでスレッドセーフとは何ですか?
  2. スレッドセーフまたはその逆であること知られている宝石のリストはありますか?
  3. スレッドセーフではないコードの一般的なパターンのリストはあります@result ||= some_methodか?
  4. などのRuby langコアのデータ構造はHashスレッドセーフですか?
  5. GVL/GILを除いて一度に 1 つの Ruby スレッドしか実行できないことを意味するMRI ではIO、スレッドセーフな変更は私たちに影響を与えますか?
4

3 に答える 3

117

コア データ構造はどれもスレッド セーフではありません。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 = 11 つの操作であるため、スレッド セーフです (内部では多くの操作が行われます。「スレッド セーフ」である理由を詳細に説明しようとすると、おそらく問題が発生するでしょうが、最終的には、代入から一貫性のない結果が得られることはありません)。 )。@n ||= 1、ではなく、他の省略形の操作 + 代入もありません。私が何度も犯した間違いの 1 つreturn unless @started; @started = trueは、まったくスレッド セーフではない という記述です。

Ruby のスレッド セーフなステートメントとスレッド セーフでないステートメントの正式なリストは知りませんが、単純な経験則があります。式が 1 つの (副作用のない) 操作のみを実行する場合、それはおそらくスレッド セーフです。例: a + bis ok, a = bis also ok, and a.foo(b)is ok,メソッドfooに副作用がない場合(Ruby ではほとんどすべてがメソッド呼び出しであり、多くの場合代入であるため、これは他の例にも当てはまります)。このコンテキストでの副作用とは、状態が変化することを意味します。副作用def foo(x); @x = x; endないわけではありません。

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スレッドセーフではないという意味ではありません。これは、適切なコンテキストで行うのが完全に正当なことです-代わりに、グローバル変数の可変状態、メソッドに渡された可変オブジェクトの処理方法、特にそれがどのように処理されるかなどを探す必要がありますオプションハッシュを処理します)。

最後に、スレッドが安全でないことは推移的なプロパティです。スレッド セーフではないものを使用するものは、それ自体がスレッド セーフではありません。

于 2013-03-03T10:35:40.080 に答える
10

Theo の回答に加えて、特に config.threadsafe に切り替える場合は、Rails で注意すべきいくつかの問題領域を追加します。

  • クラス変数:

    @@i_exist_across_threads

  • ENV :

    ENV['DONT_CHANGE_ME']

  • スレッド:

    Thread.start

于 2013-05-13T18:36:10.860 に答える
9

Rails 4以降、デフォルトではすべてがスレッド環境で実行される必要があります

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

以前は、マルチスレッド アプリ サーバーにデプロイする場合は、config.threadsafeを有効にする必要がありましたが、現在はこれがデフォルトになっています。これは、何も効果がないか、単一プロセスで実行される Rails アプリにも適用されるためです (プルーフリンク)。

しかし、Rails 4ストリーミングのすべての利点と、マルチスレッド展開のその他のリアルタイム機能が必要な場合は、この記事が興味深いものになるでしょう。@Theo 残念ながら、Rails アプリの場合、実際には、リクエスト中に変更する静的状態を省略すればよいだけです。これは従うべき単純な方法ですが、残念ながら、見つけたすべての宝石についてこれについて確信を持つことはできません. 私が覚えている限りでは、JRuby プロジェクトの Charles Oliver Nutter がこのポッドキャストでいくつかのヒントを提供していました。

また、複数のスレッドからアクセスされるデータ構造が必要な純粋な並行 Ruby プログラミングを書きたい場合は、thread_safe gem が役立つかもしれません。

于 2013-12-09T10:53:06.820 に答える