简体   繁体   English

如何生成线程安全的统一随机数?

[英]How do I generate thread-safe uniform random numbers?

My program needs to generate many random integers in some range (int min, int max). 我的程序需要在某个范围内生成许多随机整数(int min,int max)。 Each call will have a different range. 每次通话都有不同的范围。 What is a good (preferably thread-safe) way to do this? 什么是好的(最好是线程安全的)方法呢? The following is not thread-safe (and uses rand(), which people seem to discourage): 以下不是线程安全的(并使用rand(),人们似乎不鼓励):

int intRand(const int & min, const int & max)
{
    return (rand() % (max+1-min)) + min;
}

This is much slower, but uses <random> : 慢得多,但使用<random>

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

Something like this is what I'm going for (the changeParameters function doesn't exist though): 像这样的东西就是我想要的(虽然changeParameters函数不存在):

int intRand(const int & min, const int & max) {
    static std::default_random_engine generator;
    static std::uniform_int_distribution<int> distribution(0, 10);
    distribution.changeParameters(min, max);
    return distribution(generator);
}

Another option would be to make a wide range on the uniform_int_distribution and then use mod like in the first example. 另一种选择是在uniform_int_distribution上进行广泛的范围,然后在第一个例子中使用mod。 However, I'm doing statistical work, so I want the numbers to come from as unbiased of a distribution as possible (eg, if the range of the distribution used is not a multiple of (max-min), the distribution will be slightly biased). 但是,我正在进行统计工作,所以我希望数字来自尽可能无偏差的分布(例如,如果使用的分布范围不是(max-min)的倍数,则分布将略微偏置)。 This is an option, but again, I would like to avoid it. 这是一个选择,但同样,我想避免它。

SOLUTION This solution comes from the answers by @konrad-rudolph @mark-ransom and @mathk . 解决方案此解决方案来自@ konrad-rudolph @ mark-ransom和@mathk的答案。 The seeding of the random number generator is done to suit my particular needs. 随机数发生器的播种是为了满足我的特殊需要。 A more common approach would be to use time(NULL). 更常见的方法是使用时间(NULL)。 If you make many threads in the same second, they would then get the same seed though. 如果你在同一秒内制作了很多线程,那么他们就会获得相同的种子。 Even with clock() this is an issue, so we include the thread id. 即使使用clock()也是一个问题,所以我们包含了线程ID。 A drawback - this leaks memory --- one generator per thread. 缺点 - 这会泄漏内存 - 每个线程一个生成器。

#if defined (_MSC_VER)  // Visual studio
    #define thread_local __declspec( thread )
#elif defined (__GCC__) // GCC
    #define thread_local __thread
#endif

#include <random>
#include <time.h>
#include <thread>

using namespace std;

/* Thread-safe function that returns a random number between min and max (inclusive).
This function takes ~142% the time that calling rand() would take. For this extra
cost you get a better uniform distribution and thread-safety. */
int intRand(const int & min, const int & max) {
    static thread_local mt19937* generator = nullptr;
    if (!generator) generator = new mt19937(clock() + this_thread::get_id().hash());
    uniform_int_distribution<int> distribution(min, max);
    return distribution(*generator);
}

Have you tried this? 你试过这个吗?

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

Distributions are extremely cheap (they will be completely inlined by the optimiser so that the only remaining overhead is the actual random number rescaling). 分布非常便宜(它们将由优化器完全内联,因此唯一剩余的开销是实际的随机数重新缩放)。 Don't be afraid to regenerate them as often as you need – in fact, resetting them would conceptually be no cheaper (which is why that operation doesn't exist). 不要害怕像你需要的那样经常重新生成它们 - 事实上,重置它们在概念上并不便宜(这就是为什么不存在这种操作)。

The actual random number generator, on the other hand, is a heavy-weight object carrying a lot of state and requiring quite some time to be constructed, so that should only be initialised once per thread (or even across threads, but then you'd need to synchronise access which is more costly in the long run). 另一方面,实际的随机数生成器是一个承载很多状态的重量级对象,需要相当长的时间来构造,因此每个线程只应初始化一次(或者甚至跨线程,但是那么你' d需要同步访问,从长远来看这是更昂贵的)。

Make the generator static , so it's only created once. 使发生器static ,因此它只创建一次。 This is more efficient, since good generators typically have a large internal state; 这是更有效的,因为好的发电机通常具有大的内部状态; more importantly, it means you are actually getting the pseudo-random sequence it generates, not the (much less random) initial values of separate sequences. 更重要的是,它意味着您实际上获得了它生成的伪随机序列,而不是单独序列的(随机性更小的)初始值。

Create a new distribution each time; 每次创建一个新的发行版; these are typically lightweight objects with little state, especially one as simple as uniform_int_distribution . 这些通常是具有很少状态的轻量级对象,尤其是像uniform_int_distribution一样简单的对象。

For thread safety, options are to make the generator thread_local , with a different seed for each thread, or to guard it with a mutex. 对于线程安全,选项是使生成器thread_local ,每个线程使用不同的种子,或使用互斥锁保护它。 The former is likely to be faster, especially if there's a lot of contention, but will consume more memory. 前者可能更快,特别是如果存在大量争用,但会消耗更多内存。

You can use one default_random_engine per thread using Thread Local Storage. 您可以使用线程本地存储为每个线程使用一个default_random_engine

I can not tell you how to correctly use TLS since it is OS dependent. 我不能告诉你如何正确使用TLS,因为它依赖于操作系统。 The best source you can use is to search through the internet. 您可以使用的最佳来源是通过互联网进行搜索。

I am a person from the future with the same problem. 我是一个来自未来同样问题的人。 The accepted answer won't compile on MSVC 2013, because it doesn't implement thread_local (and using __declspec(thread) doesn't work because it doesn't like constructors). 接受的答案不会在MSVC 2013上编译,因为它没有实现thread_local(并且使用__declspec(thread)不起作用,因为它不喜欢构造函数)。

The memory leak in your solution can be moved off the heap by modifying everything to use placement new. 通过修改所有内容以使用新的位置,可以将解决方案中的内存泄漏移出堆。

Here's my solution (combined from a header and source file): 这是我的解决方案(从头文件和源文件组合):

#ifndef BUILD_COMPILER_MSVC
thread_local std::mt19937 _generator;
#else
__declspec(thread) char _generator_backing[sizeof(std::mt19937)];
__declspec(thread) std::mt19937* _generator;
#endif
template <typename type_float> inline type_float get_uniform(void) {
    std::uniform_real_distribution<type_float> distribution;
    #ifdef BUILD_COMPILER_MSVC
        static __declspec(thread) bool inited = false;
        if (!inited) {
            _generator = new(_generator_backing) std::mt19937();
            inited = true;
        }
        return distribution(*_generator);
    #else
        return distribution(_generator);
    #endif
}

Write a simple LCG (or whatever) PRNG for yourself, which will produce numbers up to the maximum possible required. 为自己写一个简单的LCG(或其他)PRNG,这将产生最大可能需要的数字。 Use a single static copy of the built-in RNG to seed a new local copy of your own PRNG for each new thread you generate. 使用内置RNG的单个静态副本为您生成的每个新线程为您自己的PRNG播种新的本地副本。 Each thread-local PRNG will have its own local storage, and never needs to refer to the central RNG again. 每个线程本地PRNG将具有其自己的本地存储,并且永远不需要再次引用中央RNG。

This assumes that a statistically good RNG is fine for you and that cryptographic security is not an issue. 这假设统计上良好的RNG对您来说没问题,并且加密安全性不是问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM