Is the random number generator in Haskell thread-safe?
There is a function named newStdGen
, which gives one a new std. gen every time it's called. Its implementation uses atomicModifyIORef
and thus is thread-safe.
newStdGen
is better than get/setStdGen not only in terms of thread-safety, but it also guards you from potential single-threaded bugs like this: let rnd = (fst . randomR (1,5)) <$> getStdGen in (==) <$> rnd <*> rnd
.
In addition, if you think about the semantics of newStdGen
vs getStdGen
/setStdGen
, the first ones can be very simple: you just get a new std. gen in a random state, chosen non-deterministically. On the other hand, with the get/set pair you can't abstract away the global program state, which is bad for multiple reasons.
I would suggest you to use getStdGen
only once (in the main thread) and then use the split
function to generate new generators. I would do it like this:
Make an MVar
that contains the generator. Whenever a thread needs a new generator, it takes the current value out of the MVar
, calls split
and puts the new generator back. Due to the functionality of an MVar
, this should be threadsafe.
By itself, getStdGen
and setStdGen
are not thread safe in a certain sense. Suppose the two threads both perform this action:
do ... g <- getStdGen (v, g') <- someRandOperation g setStdGen g'
It is possible for the threads to both run the g <- getStdGen
line before the other thread reaches setStdGen
, therefore they both could get the exact same generator. (Am I wrong?)
If they both grab the same version of the generator, and use it in the same function, they will get the same "random" result. So you do need to be a little more careful when dealing with random number generation and multithreading. There are many solutions; one that comes to mind is to have a single dedicated random number generator thread that produces a stream of random numbers which other threads could consume in a thread-safe way. Putting the generator in an MVar, as FUZxxl suggests, is probably the simplest and most straightforward solution.
Of course I would encourage you to inspect your code and make sure it is necessary to generate random numbers in more than one thread.