zoo
zoo

Reputation: 1911

Is the random number generator in Haskell thread-safe?

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

Answers (4)

Rotsor
Rotsor

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

Edward
Edward

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

Dan Burton
Dan Burton

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

fuz
fuz

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

Related Questions