前言: 所以我們有一點的討論正在進行關於下面的示例代碼。爭論在於下面的代碼中是否存在線程問題。我們正在尋找的是爲什麼它存在或爲什麼它不存在的一個很好的答案。Ruby線程問題還是無線程問題?
下面的例子顯示如下。一個類構建爲名爲IoBoundApiCall
,表示網絡調用。除非與討論有關,否則應該忽略這個類,如果這樣有助於使它無關緊要,在我們的產品代碼中,這是對Google API的查詢。接下來是一個循環,它建立一個包含一千個項目的數組,數組中的每個項目都是一個散列。這設置了「共享數據」。
接下來我們有問題的代碼,在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
注意:我們正在使用Ruby 2.1 MRI – GrokSrc