[解決済み] Rubyでスレッドセーフでないものを知るには?
質問
Rails 4から始める
になると、すべてがデフォルトでスレッド環境で実行されなければならなくなります。これが意味するのは、私たちが書くすべてのコードが
と
すべて
私たちが使用するgemは
threadsafe
で、これに関していくつか質問があります。
- ruby/railsでスレッドセーフでないものは何ですか? Vs ruby/railsでスレッドセーフなのは何ですか?
- 以下のような gems のリストはありますか? は がスレッドセーフであることが知られているか、あるいはその逆であるか?
-
スレッドセーフでないコードの一般的なパターンのリストがありますか?
@result ||= some_method
? -
Ruby lang core のデータ構造は、次のようなものですか?
Hash
などのデータ構造はスレッドセーフですか? -
MRIでは
GVL
/GIL
を除いて、一度に実行できるのは1つのrubyスレッドのみということになります。IO
スレッドセーフの変更は私たちに影響を与えるのでしょうか?
どのように解決するのですか?
コアとなるデータ構造のどれもがスレッドセーフではありません。私が知る限り、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のようにシングルスレッドだったとしても、並行処理について考えなければなりません。インクリメントされた変数の例はうまくいきますが、物事が非決定的な順序で起こり、あるコールバックが別のコールバックの結果を食い止めるようなレースコンディションが発生する可能性はまだあります。シングルスレッドの非同期システムは推論しやすいですが、並行性の問題がないわけではありません。複数のユーザーがいるアプリケーションを考えてみてください。2 人のユーザーがほぼ同時に Stack Overflow の投稿の編集をヒットし、投稿を編集して保存をヒットした場合、3 番目のユーザーが後で同じ投稿を読むときに、誰の変更が見られるでしょうか?
Rubyでは、他のほとんどの同時実行ランタイムと同様に、1つ以上の操作であるものはスレッドセーフではありません。
@n += 1
は複数の操作であるため、スレッドセーフではありません。
@n = 1
は1つの操作なのでスレッドセーフです(フードの下にはたくさんの操作があり、なぜ "スレッドセーフ" なのかを詳しく説明しようとすると面倒なことになるかもしれませんが、最終的には代入から矛盾した結果を得ることはないでしょう)。
@n ||= 1
はそうではありませんし、他の省略形操作+代入もそうではありません。私が何度も犯してきた間違いの1つは、次のように書くことです。
return unless @started; @started = true
と書くことです。これはスレッドセーフではありません。
Rubyのスレッドセーフな文とスレッドセーフでない文の権威あるリストは知りませんが、簡単な経験則があります:もし式が一つの(副作用のない)操作しかしないなら、それはおそらくスレッドセーフです。例えば
a + b
は大丈夫です。
a = b
もOK、そして
a.foo(b)
もOKです。
というメソッドがあれば
foo
が副作用のない
(というメソッドであれば、副作用はありません(Rubyではほとんどのものがメソッド呼び出しであり、多くの場合、代入であっても、これは他の例にも当てはまります)。この文脈での副作用とは、状態を変化させるものを意味します。
def foo(x); @x = x; end
は
ではなく
副作用がないわけではありません。
Rubyでスレッドセーフなコードを書く上で最も難しいことのひとつは、配列、ハッシュ、文字列を含むすべてのコアデータ構造がミュータブルであることです。状態の一部を誤ってリークすることは非常に簡単で、その一部がミュータブルである場合、物事は本当に台無しになることがあります。次のようなコードを考えてみてください。
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
アクセッサーを使用します。カプセル化の観点から問題があることに加え、これは同時実行の虫の知らせでもあります。誰かがその配列を受け取り、それを他のどこかに渡すと、そのコードは今度はその配列を所有していると考え、それを使ってやりたいことが何でもできるようになるかもしれません。
もうひとつの典型的な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
定数はもう同じ値を持っていません。定数は参照するものだけが一定で、参照するデータ構造の一定性を保証するものではありません。このコードが同時に実行されたらどうなるかを考えてみてください。
共有された変更可能な状態(例えば、複数のスレッドによってアクセスされるオブジェクトのインスタンス変数、複数のスレッドによってアクセスされるハッシュや配列のようなデータ構造)を避けるならば、スレッドセーフはそれほど難しいものではありません。アプリケーションの中で、同時にアクセスされる部分をなるべく少なくして、そこに力を注ぐようにしましょう。Railsアプリケーションでは、リクエストごとに新しいコントローラオブジェクトが作成されるので、単一のスレッドにしか使われません。しかし、Railsではグローバル変数(
User.find(...)
はグローバル変数
User
のように、クラスとしか思えないかもしれませんし、クラスではありますが、グローバル変数の名前空間でもあります)、これらの中には、読み取り専用なので安全なものもありますが、便利だからと、このグローバル変数に保存してしまうこともあります。グローバルにアクセスできるものを使うときは、十分注意してください。
Railsをスレッド環境で動かすことはかなり前から可能なので、Railsの専門家でなくても、Railsそのものに関してはスレッドセーフを気にする必要はないとまで言い切れます。上に書いたようなことをすれば、スレッドセーフでないRailsアプリケーションを作ることは可能です。他のgemについては、そのgemがスレッドセーフだと言わない限りスレッドセーフではないと仮定し、もしそうだと言うならそうではないと仮定して、そのコードに目を通します (ただし、次のようなコードを書いているのを見ただけで、スレッドセーフではないと判断します)。
@n ||= 1
がスレッドセーフでないことを意味するわけではなく、正しい文脈で行う完全に正当な行為です。代わりに、グローバル変数におけるミュータブルな状態、メソッドに渡されるミュータブルなオブジェクトの処理方法、特にオプションハッシュの処理方法などを調べる必要があります)。
最後に、スレッドセーフでないことは、他律的な性質です。スレッドセーフでないものを使用するものはすべて、それ自体がスレッドセーフではありません。
関連
-
[解決済み] 他のスレッドからGUIを更新するにはどうすればよいですか?
-
[解決済み] Rubyのswitch文の書き方
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
-
[解決済み] プロセスとスレッドの違いは何ですか?
-
[解決済み] Rubyで配列に値が存在するかどうかを確認する方法
-
[解決済み] Rubyからシェルコマンドを呼び出す方法
-
[解決済み] Rubyのattr_accessorとは何ですか?
-
[解決済み] ファイルのテキストをパターン検索し、指定された値で置き換える方法
-
[解決済み] define_methodを使ってクラスメソッドを作成するには?
-
[解決済み] Ruby初心者が注意すべきRuby Gotchasとは?[クローズド]
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] Rubyでシングルクォートとダブルクォートを使い分けるとパフォーマンスが上がりますか?
-
[解決済み] Rubyの継承とミキシンの比較
-
[解決済み] 2つのハッシュを比較するにはどうすればよいですか?
-
[解決済み] doブロックと中括弧{}の使い分け
-
[解決済み] Herokuの基本的なアプリのロードに2秒かかるのはなぜですか?
-
[解決済み] Ruby で改行せずに印刷する方法
-
[解決済み] Rubyで既存のハッシュに追加する方法
-
[解決済み] Rubyです。変数を文字列にマージする
-
[解決済み] メソッド名の最後にある「!」や「?」は何のため?
-
[解決済み] FactoryGirlのbuildメソッドとcreateメソッドの違いは何ですか?