Reputation: 16653
I'm having some hard time finding convincing sources on the thread safety of random number generation in C++.
The application I am working on currently has few threads accessing the random number generation, but it might have more in the future.
The first thing used was:
int randomInRange(const int min, const int max)
{
srand(time(0));
return min + (rand() % max);
}
From what I found out this is wrong in the sense that because the seed is set for every call, it impacts the statistical randomness of the rand()
function and performance negatively.
Next thing tried was using a singleton which holds a single instance of std::mt19937
:
// RandomHelper.h
class RandomHelper
{
private:
std::mt19937 engine;
// Singleton
RandomHelper() : engine(std::random_device()()) {}
public:
static RandomHelper& getInstance();
int randomInRange(int min, int max)
{
std::uniform_int_distribution<int> uidist(min, max);
return uidist(engine);
}
};
// RandomHelper.cpp
RandomHelper& RandomHelper::getInstance()
{
static RandomHelper instance;
return instance;
}
Which should help with the statistical randomness. But for this I understand that it would be better to have a std::mt19937
instance per-thread.
I am not sure I understand why.
Is this because if multiple threads would call RandomHelper::getInstance().randomInRange(.., ..)
at the exact same time they would get the same result? AKA it's not thread-safe?
To avoid the possibility of this it is now implemented as the following:
int randomInRange(const int min, const int max)
{
thread_local std::mt19937 engine(std::random_device{}());
std::uniform_int_distribution<int> uidist(min, max);
return uidist(engine);
}
This looks to be the best solution so far, but I can't find much information on the thread safety of std::mt19937
or std::random_device
to see whether it's actually needed to have a thread_local variant of those versus the single instance in the singleton.
I am willing to do more research but I have no idea on where to look other than the usual C++ references which don't mention anything about thread-safety as far as I can see.
Upvotes: 2
Views: 1192
Reputation: 23792
Random number generator engines are not thread safe.
Using thread_local
to initialize one engine per thread makes it thread safe, so I would say your solution is good.
If you have doubts about the thread safety of random_device
, maybe make it thread_local
also:
int randomInRange(const int min, const int max)
{
thread_local std::random_device rd;
thread_local std::mt19937 rng(rd());
thread_local std::uniform_int_distribution<int> uid;
return uid(rng, decltype(uid)::param_type{min,max});
}
There are more threads that talk about this that weren't mentioned:
C++ thread-safe uniform distribution random number generation
C++11 Thread safety of Random number generators
I'm sure that there are more.
Upvotes: 2