【问题标题】:What's a good way to seed a random number generator to work with forking?播种随机数生成器以使用分叉的好方法是什么?
【发布时间】:2018-08-16 17:47:40
【问题描述】:

假设我有一个函数generateId 来生成随机的 64 位整数。如果我这样写generateId

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

那么rng 不会在分叉后重新播种。跑步

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

将为孩子和父母打印相同的数字。

有没有办法我可以写 generateId 以便它为新进程重新播种但仍然保持性能。

【问题讨论】:

  • std::random_device 的另一个问题是它在 MinGW 中被破坏多年,现在仍然如此。
  • 一般来说,线程、非 POD cpp 类和fork 不能很好地配合使用。 cpp 对象没有准备好在没有调用构造函数的对象上调用析构函数(或者通常按位复制它们,这是 fork 正在做的),并且fork 也不能很好地与线程一起使用 - 它甚至有一部分手册页致力于它。我建议你坚持使用多线程或多进程。
  • 你在第一次调用函数之前尝试过fork吗?
  • @RSahu 如果真正的程序在分叉之前需要一个随机数,一般来说并没有什么帮助。显然,虽然是的,但这是可行的
  • @rustyx 你在说什么? fork() 对所有地址空间进行按位复制(好吧,实际上是 COW,但在这种情况下并不重要),包括对象,包括那些不可轻易复制的类的对象——这通常(神奇地)有效,但我有没有看到的情况。

标签: c++ c++11 random


【解决方案1】:

这是我想出的:

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();
}

它可以与分叉一起使用,但也没有为每个数字生成调用 getpid 的开销,这取决于您使用的 libc 版本可能会或可能不会被缓存,因此可能会导致性能损失。请参阅getpid 上的注释:

从 glibc 版本 2.3.4 到并包括版本 2.24,glibc getpid() 缓存 PID 的包装函数,目的是避免 进程重复调用 getpid() 时的额外系统调用。

【讨论】:

    【解决方案2】:

    这应该可行:

    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)();
    }
    

    注意:此代码不是线程安全的,我删除了thread_local,假设您尝试使用它解决fork() 问题。如果涉及多线程,则需要适当的锁定以及使用mutexes 或使用fork() 的非阻塞原语的问题的考虑

    【讨论】:

    • 哦,比赛条件的乐趣!您还从 OP 的代码中删除了 thread_local,看起来您并没有保留语义。
    • 不清楚 OP 是否使用 thread_local 来拥有不同的种子 btw 线程,或者他/她试图以这种方式解决 fork() 问题。如果第一个 OP 总是可以返回 thread_local 回来(和适当的同步),如果以后不再需要。根据代码示例,不涉及多线程,也不必假设它是。
    • 我希望它与线程一起工作,是的。 @Slava - 为什么使用 unique_ptr 而不是调用 seed
    • @mickb 没有特别的原因,我只是没有研究界面,并认为重新创建对象是最简单的重新播种方法。如果不是这种情况,你可以改变它,这段代码就是为了展示这个想法。
    • 有没有办法避免每次调用getpid
    猜你喜欢
    • 2011-07-16
    • 2015-03-14
    • 1970-01-01
    • 1970-01-01
    • 2014-01-05
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    相关资源
    最近更新 更多