2

I thought functions are thread safe if they don't modify non-local data.

My assumption is correct according to this answer. But, recently I came across this code,

int intRand(const int & min, const int & max) {
    static thread_local std::mt19937 generator;
    std::uniform_int_distribution<int> distribution(min,max);
    return distribution(generator);
}

The code left me puzzled. Why does it use thread_local if functions are already thread safe?

jeffbRTC
  • 1,941
  • 10
  • 29
  • 2
    `distribution(generator)` changes `generator`. If it was just `static` then multiple threads calling `intRand` would modify the same object. – François Andrieux Oct 29 '21 at 19:48
  • @FrançoisAndrieux It does but distribution is initialized locally. – jeffbRTC Oct 29 '21 at 19:49
  • That still doesn't change the fact that `distribution` modifies `generator`. If multiple threads call the function at the same, they will all modify `generator` at the same time, which is a data race. – NathanOliver Oct 29 '21 at 19:51
  • @FrançoisAndrieux Oh! I didn't know much about static's inner workings so it creating a variable that's life time ends only after program end. – jeffbRTC Oct 29 '21 at 19:52

2 Answers2

5

The random number generators of the standard library (including the std::mt19937 used in the example) may not be used unsequenced in multiple threads. thread_local guarantees that each thread has their own generator which makes it possible to call the function unsequenced from multiple threads.

I thought functions are thread safe if they don't modify non-local data.

Static storage is non-local. This is true even when the variable with static storage duration is a local variable. The name is local; the storage is global.

eerorika
  • 232,697
  • 12
  • 197
  • 326
1

This is for clarity only.

static thread_local std::mt19937 generator;

and

thread_local std::mt19937 generator;

are the same things.

The static is cosmetic in this case. Both are static.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108