3

I have seen several usages of std::mt19937 like following:

#include <random>

size_t get_rand()
{
    static thread_local std::mt19937 generator(time(0));
    std::uniform_int_distribution<int> distribution(0, 10);
    return distribution(generator);
}

I want to find out what benefits comes from using static thread_local rather than static here. // first static is redundant

I understand that in the second case generator has lifetime until thread is finished. Are there other benefits/differences in common or this particular case?

mouse_00
  • 593
  • 3
  • 13

1 Answers1

6

If it wasn't thread_local, then calling get_rand from multiple threads would cause a data race and therefore undefined behavior.

Although the static initialization is always safe, even with multiple threads calling it, the call to the generator in distribution(generator), which modifies the generator's internal state, is not.

With thread_local each thread has its own generator and so there is no issue with them calling the function unsynchronized.


Also note that time(0) is a bad idea. General issue with that as a seed aside, if multiple threads call the function at a similar time, they are likely to be seeded with the same value and then the random numbers in the threads will all be identical (which is very likely not what you want).

A somewhat better seeding would be

thread_local std::mt19937 generator(std::random_device{}());

instead, assuming your platform implements std::random_device as a proper source of randomness (which should generally be the case, but e.g. is not on some versions of MinGW). (Be careful though if your platform does not properly implement std::random_device. In that case it might always produce the same sequence of numbers.) For more details on seeding the standard library random number generators see e.g. this question. Also note that even with platform support, this is still a mediocre seeding since it (typically) uses only 32bits of entropy. See e.g. this blog post for some details about these issues. Correct seeding is a quite non-trivial problem.

For a declaration at block scope static is redundant by the way when using thread_local.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • 1) Theoretically I could synchronize threads, for instance using spinlock, and it would work fine (but slower)? – mouse_00 May 27 '22 at 16:14
  • "thread_local can appear together with static or extern to adjust linkage" [source](https://en.cppreference.com/w/cpp/language/storage_duration). So `static` is redundant only in some cases, like this one where the variable is restricted to the scope of a function. When a variable is declared at namespace scope, `static` can make a difference – Nikos Athanasiou May 27 '22 at 16:16
  • @mouse_00 Yes, if you make sure that the function is not called unsynchronized, using atomics, mutices or fences, then there is no issue with not using `thread_local`. But usually a random number generator is supposed to produce the numbers quickly and there is no real benefit, except saving a few bytes of storage, with not using `thread_local` here. – user17732522 May 27 '22 at 16:16
  • @NikosAthanasiou Yes, correct. I have clarified the answer. – user17732522 May 27 '22 at 16:17