Ryan Burn
Ryan Burn

Reputation: 2316

What's a good way to seed a random number generator to work with forking?

Suppose I have a function generateId to generate random 64-bit integers. If I write generateId like this

uint64_t generateId() {
  static thread_local std::mt19937_64 rng{std::random_device{}()};
  return rng();
}

Then rng won't get reseeded after a fork. Running

int main() {
  std::cout << generateId() << "\n";
  if (fork() == 0) {
    std::cout << "child: " << generateId() << "\n";
  } else {
    std::cout << "parent: " << generateId() << "\n";
  }
  return 0;
}

Will print the same number for child and parent.

Is there a way I can write generateId so that it reseeds for new processes but still remains performant.

Upvotes: 3

Views: 344

Answers (2)

Ryan Burn
Ryan Burn

Reputation: 2316

Here's what I came up with:

class TlsRandomNumberGenerator {
 public:
   TlsRandomNumberGenerator() {
     pthread_atfork(nullptr, nullptr, OnFork);
   }

   static uint64_t GenerateId() {
     return random_number_generator_();
   }
 private:
  static thread_local std::mt19937_64 random_number_generator_;

  static void OnFork() {
    random_number_generator_.seed(std::random_device{}());
  }
};

thread_local std::mt19937_64 TlsRandomNumberGenerator::random_number_generator_{
    std::random_device{}()};

uint64_t generateId() {
  static TlsRandomNumberGenerator rng;
  return TlsRandomNumberGenerator::GenerateId();
}

It will work with forking but also doesn't have the overhead of calling getpid for every number generation, which depending on the version of libc you're using may or may not be cached so can involve a performance penalty. See notes on getpid:

From glibc version 2.3.4 up to and including version 2.24, the glibc wrapper function for getpid() cached PIDs, with the goal of avoiding additional system calls when a process calls getpid() repeatedly.

Upvotes: 1

Slava
Slava

Reputation: 44258

This should work:

uint64_t generateId() {
   static pid_t mypid = 0;
   static std::unqiue_ptr<std::mt19937_64> rng;
   if( mypid != getpid() ) {
       rng.reset();
       mypid = getpid();
   }
   if( !rng )
      rng = std::make_unique<std::mt19937_64>(std::random_device{}());

   return (*rng)();
}

Note: this code is not thread safe, and I removed thread_local assuming you tried to solve fork() issue using it. If multithreading involved then proper locking is ncesessary as well as considerations on problems using mutexes or non-blocking primitives with fork()

Upvotes: 0

Related Questions