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 isnil
- thread 2 reads
h[0]
and finds it isnil
- thread 1 sets
h[0] = 0
and incrementsh[0] += 1
- thread 2 sets
h[0] = 0
and incrementsh[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.