Ruby线程问题还是没有线程问题?

时间:2016-09-15 16:32:52

标签: ruby multithreading mutex

前言 因此,我们对下面的示例代码进行了一些讨论。争论的焦点是下面的代码中是否存在线程问题。我们正在寻找的是为什么它存在或为什么不存在的良好答案。

以下示例显示以下内容。构造一个名为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
.
.
.

线程问题输出:未预测的输出类似于以下内容。请注意,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

1 个答案:

答案 0 :(得分:1)

使用官方Ruby VM(YARV),没有线程问题。 YARV完全是线程不安全的,所以基本上每次触摸Ruby对象时,全局VM锁(GVL)都会阻塞所有线程,但是会阻止对象因多个线程相互踩踏而进入无效状态。

此代码导致问题的唯一方法是更新输入对象会导致VM的内部状态出现一些副作用,这与另一个同时更新不同输入的线程冲突。但这正是GVL所阻止的。