
Rather than having a separate thread computing the random numbers using IO, why not just stick an StdGen in a TVar and write a function like:
type RandomVar = TVar StdGen
rnd :: RandomVar -> STM a rnd var = do g <- readTVar var let (r,g') = random g writeTVar var g' return r
The user of this approach should be aware that it may lead to non-determinism. That is, the sequence of psuedo-random numbers extracted by any one thread will depend on those extracted by other threads, which may in turn depend on the scheduling of those threads. While non-determinism might be ideal for some applications of psuedo-random numbers, there are other applications (simulation, in particular) where it is useful to be able to reproduce an exact sequence of psuedo-random events by just reapplying the same initial seed. To do that, you would need (among other things) to thread a separate pseudo-random generator through each thread (using the approach described by Robert and Henning), and split the psuedo-random generator whenever forking a new thread. The TVar approach might also lead to excessive STM transaction abort-retry cycles, if multiple threads are retrieving many pseudo-random numbers via a single TVar.