Nic*_* M. 5 ruby multithreading hashmap thread-safety
我对 Ruby 中哈希值的线程安全性感到好奇。从控制台运行以下命令(Ruby 2.0.0-p247):
h = {}
10.times { Thread.start { 100000.times {h[0] ||= 0; h[0] += 1;} } }
Run Code Online (Sandbox Code Playgroud)
回报
{0=>1000000}
Run Code Online (Sandbox Code Playgroud)
这是正确的期望值。
为什么它有效?我可以依靠此版本的 Ruby 实现线程安全的哈希值吗?
编辑:测试100次:
counter = 0
100.times do
h={}
threads = Array.new(10) { Thread.new { 10000.times { h[0] ||= 0; h[0] += 1 } } }
threads.map { |thread| thread.join }
counter += 1 if h[0] != 100000
end
puts counter
Run Code Online (Sandbox Code Playgroud)
计数器最后还是0。我尝试了多达 10K 次,并且这段代码从未出现过任何线程安全问题。
不,您不能依赖哈希是线程安全的,因为它们不是为线程安全而构建的,很可能是出于性能原因。为了克服标准库的这些限制,创建了 Gems,它提供线程安全(并发 ruby)或不可变(仓鼠)数据结构。这些将使访问数据线程安全,但除此之外您的代码还有一个不同的问题:
你的输出将不是确定性的;事实上,我尝试了几次你的代码,一旦我得到了544988结果。在您的代码中,可能会发生经典的竞争条件,因为涉及单独的读取和写入步骤(即它们不是原子的)。考虑一下这个表达式h[0] ||= 0,它基本上可以翻译为h[0] || h[0] = 0. 现在,很容易构造一个发生竞争条件的情况:
h[0]并发现它是nilh[0]并发现它是nilh[0] = 0和增量h[0] += 1h[0] = 0和增量h[0] += 1{0=>1}虽然正确的结果是{0=>2}如果你想确保你的数据不会被损坏,你可以使用互斥锁来锁定操作:
require 'thread'
semaphore = Mutex.new
h = {}
10.times do
Thread.start do
semaphore.synchronize do
100000.times {h[0] ||= 0; h[0] += 1;}
end
end
end
Run Code Online (Sandbox Code Playgroud)
注意:这个答案的早期版本提到了“thread_safe”gem。“thread_safe”自 2017 年 2 月起已弃用,成为“concurrent-ruby”gem 的一部分。改用那个。