多线程中的循环

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会被执行更多次?


join(2) 应该做什么? - uday
它会给线程设置一个时间限制(以秒为单位)来终止。如果我不调用它,当程序运行到结束时,Ruby 将自动回收线程(因此无限的 loop do 也将有个终结)。请参见 http://www.ruby-doc.org/core-1.9.3/Thread.html#method-i-join 了解更多信息。 - Tombart
1
很有趣。在 Ruby 1.8 中,“差异”始终为<> 0,计数从未相差超过1,但在 Ruby 1.9 中,“差异”始终为== 0,但 count1 和 count2 相距甚远。 - Casper
2个回答

3

执行

puts "count1 :  #{count1}"

需要翻译的内容:

需要一些时间(虽然可能很短)。 它不是一瞬间完成的。 因此,这两行相邻的代码并不神秘:

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

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

同样地,当

difference += (count1 - count2).abs

在计算的过程中,原则上当引用 count2 之前引用了 count1,计数器可能会增加。但是,在此期间未执行任何命令,并且我猜测引用 count1 所需的时间要比 counter 线程再次循环所需的时间要短得多。请注意,在前者中进行的操作是后者中所做操作的一个合适子集。如果差异足够大,这意味着在 - 方法的参数调用期间,counter 线程尚未经过一次循环周期,则 count1count2 将显示为相同的值。

一种预测是,如果在引用 count1 之后但在引用 count2 之前放置一些昂贵的计算,则 difference 将显现出来:

difference += (count1.tap{some_expensive_calculation} - count2).abs
# => larger `difference`

实际上,在计数器线程之间放置类似于 sleep 0.001 的东西也会使差异显示出来。由于计数器线程正在“忙循环”,我想知道间谍线程在1.9中是否有机会运行。立即放置睡眠语句可以释放CPU时间以运行间谍线程,并且差异将显示出来。 - Casper
1
谢谢,这很有道理。将 puts 替换为 print "count1 : #{count1}, count2 : #{count2}\n" 可以减少计数器之间的差异。因为 puts 被执行为两个命令,所以需要更多时间。 - Tombart

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 中能够工作纯粹是“幸运”,因为无论是在 1.8 还是 1.9 中,当 join(2) 返回时,线程都没有完成执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接