I don’t really have an answer, but just few comments.
That would really depend on the RNG type. Generating a new MersenneTwister on each iteration might be very expensive, whereas StableRNG is very cheap to create.
So that might be the most general solution, as it works also for expensive-to-create RNGs.
What is thread-safe is to use the “global RNG” concurrently, as in fact there a multiple “global” RNGs, one for each thread. But it’s unsafe to use one given RNG concurrently, as is the case for most RNGs.
I think your solution 3. above is the one generally shared on Julia forums for that.
I think that really depends on the RNG type, i.e. how it does initialization. Note also that StableRNG was not really intended for this use case; it currently accepts a seed in 0 <= seed <= typemax(UInt) (but it could be made to accept a seed almost equal to typemax(UInt128)), but no facility is provided to produce two StableRNGs which are as much not correlated as possible. But maybe random seeds would be good enough.