Ruby线程是否安全?

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

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

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

ps如果你说,默认情况下它不是线程安全的,你可以分享一个简单的例子,当我在rails console中运行时会给我不同的结果吗?

编辑:

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

如果我添加puts ,那么i打印顺序将会改变。 它意味着线程彼此交错,但它们是否同时运行?

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

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

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

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

  AB ------------- READ READ INCR INCR WRITE WRITE 

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

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

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

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

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

 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 => [#, #, #] irb(main):020:0> threads.each {|t| t.join} items3 = 152 items2 = 137 items1 = 101 => [#, #, #] irb(main):021:0> 

这是在Ruby中线程化进程的基本示例。 你有一个main方法calculate_sum ,它将一个数组作为参数@item1, @item2, @item3 。 从那里你创建三个线程threads = (1..3)将它们映射到自己的变量.map do |i| 并使用线程映射到的变量Thread.start(i)启动一个新的Thread实例。

从这里你创建一个项变量,它等于实例变量是items = instance_variable_get()输出计算结果, puts "items#{} = #{calculate_sum(items)}"

如您所见,线程开始同时运行=> [#, #, #] 。 线程都是通过调用每个线程并将它们连接起来执行的threads.each {|t| t,join} 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 => # irb(main):030:0> hello goodbye 

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

在主示例中,结尾有=> [#, #, #]因为所有线程都完成了该过程,并立即退出。 为了让我的进程完成,你必须为第二个线程提供一个exit

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

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

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

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

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

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

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

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

对于像JRuby,Rubinius,IronRuby,MacRuby等实现并且具有可以并行运行的线程的实现而言,这是一个艰难的过程。 他们仍在努力找出答案。

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

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