use*_*783 4 ruby multithreading thread-safety
我使用了以下 Python 代码来表明i在多个线程上递增整数不是线程安全的:
>>> i = 0
>>> def increment_i():
... global i
... for j in range(1000): i += 1
...
>>> threads = [threading.Thread(target=increment_i) for j in range(10)]
>>> for thread in threads: thread.start()
...
>>> for thread in threads: thread.join()
...
>>> i
4858 # Not 10000
Run Code Online (Sandbox Code Playgroud)
我试图将此代码转换为 Ruby,期望得到类似的结果:
> $i = 0
> def increment_i()
> for j in 0...1000 do $i += 1 end
> end
> threads = (0...10).map { Thread.start { increment_i } }
> for thread in threads do thread.join end
Run Code Online (Sandbox Code Playgroud)
如果我在 1.8 版上运行这个 Ruby 代码,它的行为确实与 Python 代码相同,留下$i的值小于 10000。
但是,在 1.9 和 2.3 上,$i似乎总是设置为恰好 10000。
+=在 Ruby 1.9 及更高版本中使用实际上线程安全的整数递增吗?
不幸的是,Ruby 没有像 Java 或 C++ 那样定义明确的内存模型。因此,关于线程安全和原子性问题的一般答案是:没有人知道,但你应该假设最坏的情况。
无法知道任何特定 Ruby 实现的行为方式。正如您所注意到的,MRI 和 YARV 的行为不同。(您假设差异是由于 Ruby 1.8 与 Ruby 1.9,但这是错误的,差异是由于 MRI 与 YARV。)Rubinius 可能以一种或另一种方式表现,或者完全不同的方式。Opal、Topaz、Ruby+OMR、TruffleRuby、MagLev、MRuby、IronRuby、JRuby 以及未来可能出现的任何其他产品也是如此。
但是,在这种特定情况下,我们实际上可以得到比一般“无人知晓”更令人满意的答案:
$i += 1
Run Code Online (Sandbox Code Playgroud)
只是语法糖
$i = $i + 1
Run Code Online (Sandbox Code Playgroud)
这显然是两个操作,因此显然是非原子的。它似乎适用于 YARV 的事实是由于无法保证的私有内部实现细节,可能随时更改,恕不另行通知,并且可能存在或可能不存在于其他实现中。