Thread-safety for hashes in Ruby
hashmap, multithreading, ruby, thread-safety
Solution
No, you cannot rely on Hashes being thread safe, because they aren't built to be thread safe, most probably for performance reasons. In order to overcome these limitations of the standard library, Gems have been created which provide thread safe (concurrent-ruby) or immutable (hamster) data structures. These will make accessing the data thread safe, but your code has a different problem in addition to that:
Your output will not be deterministic; in fact, I tried you code a few times and once I got `544988` as result. In your code, a classical race condition can occur because there are separate reading and writing steps involved (i.e. they are not atomic). Consider the expression `h[0] ||= 0`, which basically translates to `h[0] || h[0] = 0`. Now, it is easy to construct a case where a race condition occurs:
- thread 1 reads `h[0]` and finds it is `nil`
- thread 2 reads `h[0]` and finds it is `nil`
- thread 1 sets `h[0] = 0` and increments `h[0] += 1`
- thread 2 sets `h[0] = 0` and increments `h[0] += 1`
- the resulting hash is `{0=>1}` although the correct result would be `{0=>2}`
If you want to make sure that your data will not be corrupted, you can lock the operation with a mutex:
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
NOTE: An earlier version of this answer mentioned the 'thread_safe' gem. 'thread_safe' is deprecated since Feb 2017, becoming part of the 'concurrent-ruby' gem. Use that one instead.
Problem
I'm curious about thread safety for hashes in Ruby. Running the following from the console (Ruby 2.0.0-p247): ``` h = {} 10.times { Thread.start { 100000.times {h[0] ||= 0; h[0] += 1;} } } ``` returns ``` {0=>1000000} ``` which is the correct expected value. Why does it work? Can I rely on hashes being thread-safe with this version of Ruby? Edit: Testing 100 times: ``` 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 ``` Counter is still 0 at the end. I tried up to 10K times and never had a single thread-safety issue with this code.