默认情况下是Ruby线程安全吗?

时间:2016-06-23 17:17:13

标签: ruby-on-rails ruby multithreading

我在控制台中运行了以下代码超过10次,结果输出相同100000

i = 0
1000.times do
Thread.start { 100.times { i += 1 } }
end
i

当我使用多个线程阅读和更新i时,它不应该给我不同的输出。它让我想知道,默认情况下ruby实际上是线程安全的吗?如果没有,那为什么我总是看到相同的输出?

P.S。如果你说,默认情况下它不是线程安全的,那么你可以分享一个简单的例子,当我在rails控制台中运行时会给我不同的结果吗?

修改

换句话说,上面的代码是同时运行1000个线程吗?如果是,则结果不应为100000 始终。如果没有,那么如何同时运行多个线程?

如果我添加puts,则i的打印顺序将发生变化。它意味着线程彼此交错,但它们是否同时运行?

我不是在问,如何使这个线程安全。我理解mutex / locking&的概念同步/异步过程。因为我理解它们,所以我无法理解这段代码的输出。

4 个答案:

答案 0 :(得分:3)

没有代码是自动线程安全的,你必须努力使其线程安全。

特别是+=操作实际上是三个操作:读取,增量,写入。如果这些与其他线程混合在一起,则可能会产生极不可预测的行为。

在两个线程上考虑以下一系列事件:

  A       B
-------------
READ
         READ
INCR
         INCR
WRITE
         WRITE

这是最简单的情况,你将有两个增量操作,因为它们都使用相同的原始值,其中一个是无效的。

在我的测试中,这种情况不太可能发生在双核系统上,但实际上是四个核心机器上的一个常见问题,因为许多行为就像两个松散连接的双核系统,每个系统都有自己的缓存。使用JRuby时,线程支持要好得多,这一点更加明显。您的示例代码为我提供了随机答案,范围从98200到99500.

为了使此线程安全,必须使用Mutex或使用Concurrent Ruby等库中的原子增量操作,这将为您提供安全执行此操作的工具。

另一种方法是避免在线程之间混合数据或使用Queue之类的结构来管理通信。没有Mutex,任何两个线程都不应该操纵同一个对象。

答案 1 :(得分:1)

咦!!最后,我找到了一种方法来证明,它不会导致100000总是在irb上。

运行以下代码给了我这个想法,

100.times do
i = 0
1000.times do
Thread.start { 100.times { i += 1 } }
end
puts i
end

在大多数情况下,我看到了不同的价值观。大多数情况下,它的范围是91k to 100000

答案 2 :(得分:1)

在计算机科学中,执行的线程是可以由操作系统调度程序独立管理的最小程序指令序列。线程是一个轻量级的过程。

<1>
<a>test1</a>
<a>test2</a>
</1> 

这是在Ruby中线程化进程的基本示例。您有一个主要方法irb(main):001:0> def calculate_sum(arr) irb(main):002:1> sleep(2) irb(main):003:1> sum = 0 irb(main):004:1> arr.each do |item| irb(main):005:2* sum += item irb(main):006:2> end irb(main):007:1> sum irb(main):008:1> end => :calculate_sum irb(main):009:0> irb(main):010:0* @items1 = [12, 34, 55] => [12, 34, 55] irb(main):011:0> @items2 = [45, 90, 2] => [45, 90, 2] irb(main):012:0> @items3 = [99, 22, 31] => [99, 22, 31] irb(main):013:0> irb(main):014:0* threads = (1..3).map do |i| irb(main):015:1* Thread.new(i) do |i| irb(main):016:2* items = instance_variable_get("@items#{i}") irb(main):017:2> puts "items#{i} = #{calculate_sum(items)}" irb(main):018:2> end irb(main):019:1> end => [#<Thread:0x2158ab8@(irb):15 run>, #<Thread:0x2158860@(irb):15 run>, #<Thread:0x2158488@(irb):15 run>] irb(main):020:0> threads.each {|t| t.join} items3 = 152 items2 = 137 items1 = 101 => [#<Thread:0x2158ab8@(irb):15 dead>, #<Thread:0x2158860@(irb):15 dead>, #<Thread:0x2158488@(irb):15 dead>] irb(main):021:0> ,它将数组作为参数calculate_sum。从那里你创建三个线程@item1, @item2, @item3将它们映射到它们自己的变量threads = (1..3)并使用线程映射到的变量.map do |i|启动一个新的Thread实例。

从这里创建一个项变量,它等于实例变量Thread.start(i)输出计算结果items = instance_variable_get(<object>)

正如您所见,线程开始同时运行puts "items#{<thread-variable>} = #{calculate_sum(items)}"。线程都是通过调用每个线程并加入它们=> [#<Thread:0x2158ab8@(irb):15 run>, #<Thread:0x2158860@(irb):15 run>, #<Thread:0x2158488@(irb):15 run>]来执行的。

最后一部分是最重要的,线程都是同时运行和死亡,但是,如果一个线程有一个非常长的进程,那么线程必须在程序结束之前结束。例如:

threads.each {|t| t,join}

第二个线程永远不会退出,因此它将继续运行该过程,直到您执行该命令为止。

在主示例中,结尾有irb(main):023:0> Thread.new do irb(main):024:1* puts t irb(main):025:1> Thread.new do irb(main):026:2* sleep(5) irb(main):027:2> puts h irb(main):028:2> end irb(main):029:1> end => #<Thread:0x2d070f8@(irb):23 run> irb(main):030:0> hello goodbye ,因为所有线程都完成了该过程,并立即退出。为了完成我的流程,您必须为第二个帖子提供=> [#<Thread:0x2158ab8@(irb):15 dead>, #<Thread:0x2158860@(irb):15 dead>, #<Thread:0x2158488@(irb):15 dead>]

我希望这能回答你的问题。

答案 3 :(得分:0)

不幸的是,自从Java 5或C ++从C ++ 11开始,Ruby就没有像Java这样的官方指定的内存模型。

事实上,Ruby确实没有官方规范,但是已经有多次尝试,所有这些都有同样的问题,Ruby的设计者并没有这样做。实际上使用它们。因此,Ruby所拥有的唯一规范基本上是#34;无论YARV做什么&#34;。 (并且,例如,ISO Ruby语言规范根本没有指定Thread类,因此完全侧面解决问题。)

但是!!! 对于并发性,这基本上是不可用的,因为YARV无法并行运行线程,因此YARV中不会出现很多并发问题,因此核心库没有& #39; t防止这些问题!但是,如果我们说Ruby的并发语义是YARV所做的,那么现在的问题就变成了:我们不能将并行性作为语义的一部分吗?核心库是否不受语义保护?

像JRuby,Rubinius,IronRuby,MacRuby等实现的那些具有可并行运行的线程的实现正面临着困难。他们仍在努力找出答案。

所以, tl; dr 回答你的问题是:我们不知道Ruby是否是线程安全的,因为我们不知道Ruby的线程语义是什么

例如,多线程程序在YARV上正常运行以打破JRuby是很常见的,但同样,它是程序的错还是JRuby?我们无法说明,因为我们没有规范告诉我们Ruby实现的多线程行为应该是什么。我们可以轻松地说出来,好吧,Ruby就是YARV所做的,当程序在YARV上运行时,我们必须更改JRuby,以便程序也适用于YARV。然而,并行性实际上是人们首先选择JRuby的主要原因之一,所以这根本不可行。