5

我有以下代码(来自 Ruby 教程):

require 'thread'

count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      count1 += 1
      count2 += 1
   end
end
spy = Thread.new do
   loop do
      difference += (count1 - count2).abs
   end
end
sleep 1

puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"
counter.join(2)
spy.join(2)
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

这是使用Mutex.synchronize. 在我的电脑上,结果与教程完全不同。调用 后join,计数有时相等:

count1 :  5321211
count2 :  6812638
difference : 0
count1 :  27307724
count2 :  27307724
difference : 0

有时不是:

count1 :  4456390
count2 :  5981589
difference : 0
count1 :  25887977
count2 :  28204117
difference : 0

我不明白0即使计数显示的数字非常不同,差异怎么可能仍然存在。

add操作大概是这样的:

val = fetch_current(count1)
add 1 to val
store val back into count1

和类似的东西count2。Ruby 可以在线程之间切换执行,因此它可能无法完成对变量的写入,但是当 CPU 回到线程时,它应该从中断的那一行继续,对吧?

并且仍然只有一个线程正在写入变量。怎么可能在loop do块内count2 += 1执行更多次?

4

2 回答 2

3

执行

puts "count1 :  #{count1}"

需要一些时间(尽管可能很短)。它不是在一个实例中完成的。因此,连续的两行并不神秘:

puts "count1 :  #{count1}"
puts "count2 :  #{count2}"

显示不同的计数。简单地说,counter线程经历了一些循环周期,并在puts执行第一个循环时增加了计数。

同样,当

difference += (count1 - count2).abs

计算时,原则上计数可以在count1被引用之前递增count2。但是在那个时间跨度内没有执行任何命令,我猜它引用count1的时间比counter线程经过另一个循环所用的时间短得多。请注意,前者完成的操作是后者完成的操作的适当子集。如果差异足够显着,这意味着counter线程在方法的参数调用期间没有经过循环循环-,那么count1count2将显示为相同的值。

一个预测是,如果您在引用之后count1但在引用之前进行一些昂贵的计算count2,那么difference将会出现:

difference += (count1.tap{some_expensive_calculation} - count2).abs
# => larger `difference`
于 2012-10-25T08:53:41.360 回答
0

这是答案。我认为您已经假设线程在join(2)返回后停止执行。

不是这种情况!即使join(2)将执行(暂时)返回到主线程,线程也会继续运行。

如果您将代码更改为此,您将看到会发生什么:

...
counter.join(2)
spy.join(2)

counter.kill
spy.kill

puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

这在 ruby​​ 1.8 中似乎有点不同,在主线程执行时线程似乎没有机会运行。

本教程可能是为 ruby​​ 1.8 编写的,但线程模型从那时起在 1.9 中发生了变化。

事实上,它在 1.8 中工作是纯粹的“运气”,因为在join(2)1.8 和 1.9 都没有返回时线程没有完成执行。

于 2012-10-25T09:02:52.637 回答