Rails 4以降、デフォルトではすべてがスレッド環境で実行される必要があります。これが意味することは、私たちが書くコードのすべてであると ALL我々が使用する宝石があることが要求されていますthreadsafe
だから、私はこれについていくつか質問があります:
Rails 4以降、デフォルトではすべてがスレッド環境で実行される必要があります。これが意味することは、私たちが書くコードのすべてであると ALL我々が使用する宝石があることが要求されていますthreadsafe
だから、私はこれについていくつか質問があります:
回答:
コアデータ構造はスレッドセーフではありません。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投稿の編集をヒットした場合、投稿の編集にしばらく時間を費やしてから保存をクリックします。その変更は、後で3人目のユーザーに表示されます同じ投稿を読みますか?
Rubyでは、他のほとんどの並行ランタイムと同様に、複数の操作を行うとスレッドセーフではありません。@n += 1
複数の操作であるため、スレッドセーフではありません。@n = 1
これは1つの操作であるため、スレッドセーフです(フード内では多くの操作が行われます。「スレッドセーフ」である理由を詳細に説明しようとすると、おそらく問題が発生しますが、割り当てから一貫性のない結果が得られることはありません。 )。@n ||= 1
、ではなく、他の省略操作+割り当てもありません。私が何度も犯した1つの間違いはを書くことですがreturn unless @started; @started = true
、これはスレッドセーフではありません。
Rubyのスレッドセーフステートメントと非スレッドセーフステートメントの正式なリストは知りませんが、簡単な経験則があります。式が1つの(副作用のない)操作のみを実行する場合は、おそらくスレッドセーフです。例:a + b
is ok、a = b
is ok、and a.foo(b)
ok、if the method foo
is side-effect free(なぜなら、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アプリケーションを作成できます。それが来るとき、他の宝石は彼らがそうであると言わない限り彼らがスレッドセーフではないと仮定し、彼らがそうではないと仮定していると彼らが言った場合、彼らのコードを調べます(しかし、彼らが@n ||= 1
スレッドセーフではないという意味ではありません。これは、適切なコンテキストで実行することは完全に正当なことです。代わりに、グローバル変数で変更可能な状態、メソッドに渡された変更可能なオブジェクトを処理する方法、特にその方法を調べる必要があります。オプションのハッシュを処理します)。
最後に、スレッドが安全でないことは推移的な特性です。スレッドセーフでないものを使用するものは、それ自体がスレッドセーフではありません。
STANDARD_OPTIONS = {...}.freeze
浅い変異に調達する
@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
[...]のようなコードを記述した場合、その後の共有変数の値は確定的ではありません。」-これがRubyのバージョン間で異なるかどうか知っていますか?例えば、1.8上でコードを実行するとの異なる値を与え@n
たが、後に1.9と上では一貫して与えるようで@n
300に等しい
Rails 4以降、デフォルトではすべてがスレッド環境で実行される必要があります
これは100%正しくありません。スレッドセーフなRailsはデフォルトでオンになっています。Passenger(コミュニティ)やUnicornなどのマルチプロセスアプリサーバーにデプロイする場合、違いはありません。この変更は、PumaまたはPassenger Enterprise> 4.0などのマルチスレッド環境にデプロイする場合にのみ関係します。
以前は、マルチスレッドのアプリケーションサーバーにデプロイする場合、config.threadsafeをオンにする必要がありました。これは、デフォルトでは無効になっているか、単一のプロセスで実行されているRailsアプリにも適用されていたためです(Prooflink)。
しかし、Rails 4 ストリーミングのメリットや、マルチスレッドデプロイメントのその他のリアルタイム機能がすべて必要な場合は、この記事が興味深いでしょう。@Theoが悲しいように、Railsアプリの場合、実際には、リクエスト中に静的な状態を変更することを省略しなければなりません。これは従うべき簡単な方法ですが、残念ながら、見つけたすべての宝石についてこれについて確信が持てません。私が覚えている限り、JRubyプロジェクトのCharles Oliver Nutterがこのポッドキャストでそれについていくつかのヒントを持っていました。
そして、もしあなたが純粋な並行Rubyプログラミングを書きたければ、複数のスレッドによってアクセスされるいくつかのデータ構造が必要になるでしょう、おそらくthread_safe gemが役立つでしょう。