Ruby 线程问题还是无线程问题?
Ruby Threading Issue or No Threading Issue?
前言:
因此,我们对下面的示例代码进行了一些讨论。争论的焦点是下面的代码中是否存在线程问题。我们正在寻找的是一个很好的答案,可以解释为什么它存在或为什么不存在。
下面的示例显示了以下内容。 class 被构造为 IoBoundApiCall
表示网络调用。这个 class 应该被忽略,除非它出于某种原因与讨论相关,如果是这样有助于使其无关紧要,我们将不胜感激。在我们的生产代码中,这是对 Google API 的查询。接下来是一个循环,它设置了一个包含一千个项目的数组,数组中的每个项目都是一个哈希。这设置了 'shared data'.
接下来是有问题的代码,一个以 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
.
.
.
线程问题输出: 意外输出如下所示。请注意 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 VM (YARV),不存在线程问题。 YARV 是完全线程不安全的,因此基本上每次您触摸 Ruby 对象时,全局 VM 锁 (GVL) 都会阻止所有线程,但一个线程会阻止由于多个线程相互踩踏而导致对象进入无效状态。
如果更新输入对象会在 VM 的内部状态中引起一些副作用,这可能与同时更新不同输入的另一个线程发生冲突,则此代码可能会导致问题的唯一方式。但这正是 GVL 所阻止的。
前言: 因此,我们对下面的示例代码进行了一些讨论。争论的焦点是下面的代码中是否存在线程问题。我们正在寻找的是一个很好的答案,可以解释为什么它存在或为什么不存在。
下面的示例显示了以下内容。 class 被构造为 IoBoundApiCall
表示网络调用。这个 class 应该被忽略,除非它出于某种原因与讨论相关,如果是这样有助于使其无关紧要,我们将不胜感激。在我们的生产代码中,这是对 Google API 的查询。接下来是一个循环,它设置了一个包含一千个项目的数组,数组中的每个项目都是一个哈希。这设置了 'shared data'.
接下来是有问题的代码,一个以 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
.
.
.
线程问题输出: 意外输出如下所示。请注意 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 VM (YARV),不存在线程问题。 YARV 是完全线程不安全的,因此基本上每次您触摸 Ruby 对象时,全局 VM 锁 (GVL) 都会阻止所有线程,但一个线程会阻止由于多个线程相互踩踏而导致对象进入无效状态。
如果更新输入对象会在 VM 的内部状态中引起一些副作用,这可能与同时更新不同输入的另一个线程发生冲突,则此代码可能会导致问题的唯一方式。但这正是 GVL 所阻止的。