2016-09-15 9 views
0

はじめに: 以下のサンプルコードについて、少し議論します。議論は、下のコードにスレッドの問題が存在するかどうかです。私たちが探しているのは、なぜそれが存在するのか、なぜ存在しないのかに対する良い答えです。Rubyスレッドの問題またはスレッドの問題はありませんか?

以下の例は、以下を示しています。ネットワークコールを表すIoBoundApiCallという名前のクラスが構築されます。このクラスは、何らかの理由でディスカッションに関連しない限り、無視する必要があります。そうであれば、無関係にすることをお手伝いします。 Googleのプロダクションコードでは、これはGoogle APIへのクエリです。それに続いて、1000個のアイテムの配列を設定するループがあり、配列の各アイテムはハッシュです。これにより、「共有データ」が設定されます。

次に、100のグループにまとめられたループを作成します。各バッチは100スレッドを生成し、疑似api呼び出しを行い、その結果をハッシュに戻します。ループの結果は検査のためにyamlに出力されます。ミューテックスは使用されないことに注意してください。

プログラム出力:正しいプログラム出力は次のようになります。

--- 
- :id: '1' 
    :data: string1 
    :results: 
    - '0': id local_string1 slept for 1 
    - '1': id local_string1 slept for 1_copy 
- :id: '2' 
    :data: string2 
    :results: 
    - '0': id local_string2 slept for 0 
    - '1': id local_string2 slept for 0_copy 
. 
. 
. 

スレッディング発行出力: Unexepcted出力は次のようになります。次のコードでは、それは結果が間違っているハッシュで保存されます場所競合状態が存在することが可能です:string1の結果が誤ってstring2

--- 
- :id: '1' 
    :data: string1 
    :results: 
    - '0': id local_string2 slept for 0 
    - '1': id local_string2 slept for 0_copy 
- :id: '2' 
    :data: string2 
    :results: 
    - '0': id local_string1 slept for 1 
    - '1': id local_string1 slept for 1_copy 
. 
. 
. 

質問とペアになっていることに注意してください?なぜ、あるいは、なぜそうしないのか。

#!/usr/bin/env ruby 
require 'bundler' 
require 'yaml' 

Bundler.require 

# What this code is doesn't really matter. It's a network bound API service call. 
# It's only here to make the example below work. Please ignore this class 
class IoBoundApiCall 
    def query(input) 
    randomly = rand(0.0..1.0) 
    sleep randomly 
    ["id #{input} slept for #{randomly}", "id #{input} slept for #{randomly}_copy"] 
    end 
end 

api = IoBoundApiCall.new 

inputs = [] 

(1..1000).each do |i| 
    inputs << { 
    id: "#{i}", 
    data: "string#{i}", 
    results: [] 
    } 
end 

# This is the code in question 
inputs.each_slice(100) do |batch| 
    threads = [] 
    batch.each do |input| 
    threads << Thread.new do 
     data_from_hash = input[:data] 
     thread_local_string = "local_#{data_from_hash}" 

     questionable_results = api.query(thread_local_string) 
     questionable_results.each_with_index do |questionable_result, i| 
     result = {} 
     result["#{i}"] = questionable_result 

     # DANGER WILL ROBINSON!! THREADING ISSUE?? 
     input[:results] << result 
     end 
    end 
    end 
    threads.map(&:join) 
end 

puts inputs.to_yaml 
+0

注:Ruby 2.1 MRIを使用しています – GrokSrc

答えて

1

公式のRuby VM(YARV)では、スレッドの問題はありません。 YARVは完全にスレッドセーフではないので、本質的にRubyオブジェクトに触れるたびに、グローバルVMロック(GVL)はすべてのスレッドをブロックしますが、複数のスレッドが互いにステッピングするためオブジェクトが無効な状態にならないようにします。

このコードが問題を引き起こす唯一の方法は、入力オブジェクトを更新すると、VMの内部状態が何らかの副作用を引き起こし、異なる入力を同時に更新している別のスレッドと競合する場合です。しかしそれはまさにGVLが防ぐものです。

+0

GVLはRubyのネイティブC関数をスレッドセーフにします! それ以外の場合、RubyにMutexは必要ありません。 –

+0

このシナリオで唯一可能性のある競合は、共有入力オブジェクトを含みます。これは...ネイティブC関数を介してのみ変更できます! – Max