Reputation: 1911
Is the same "global random number generator" shared across all threads, or does each thread get its own?
If one is shared, how can I ensure thread-safety? The approach using getStdGen and setStdGen described in the "Monads" chapter of Real World Haskell doesn't look safe.
If each thread has an independent generator, will the generators for two threads started in rapid succession have different seeds? (They won't, for example, if the seed is a time in seconds, but milliseconds might be OK. I don't see how to get a time with millisecond resolution from Data.Time.)
Upvotes: 15
Views: 1039
Reputation: 13793
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.
Upvotes: 14
Reputation: 1826
You can use split
as in FUZxxl's answer. However, instead of using an MVar, whenever you call forkIO
, just have your IO action for the forked thread close over one of the resulting generators, and leave the other one with the original thread. This way each thread has its own generator.
As Dan Burton said, do inspect your code and see if you really need RNG in multiple threads.
Upvotes: 2
Reputation: 53725
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.
Upvotes: 3
Reputation: 93172
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.
Upvotes: 10