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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21237905/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:33:15  来源:igfitidea点击:

How do I generate thread-safe uniform random numbers?

c++multithreadingrandom

提问by PThomasCS

My program needs to generate many random integers in some range (int min, int max). Each call will have a differentrange. 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):

我的程序需要在某个范围内生成许多随机整数(int min,int max)。每次调用都会有不同的范围。什么是好的(最好是线程安全的)方法来做到这一点?以下不是线程安全的(并且使用了人们似乎不鼓励的 rand()):

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

This is muchslower, 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_distributionand then use mod like in the first example. However, I'm doing statistical work, so I want the numbers to come from as unbiased of a distribution as possible (e.g., if the range of the distribution used is not a multiple of (max-min), the distribution will be slightly biased). This is an option, but again, I would like to avoid it.

另一种选择是uniform_int_distribution在第一个例子中制作一个广泛的范围,然后使用 mod 。但是,我正在做统计工作,所以我希望数字来自尽可能无偏的分布(例如,如果使用的分布范围不是 (max-min) 的倍数,分布将略微有偏见)。这是一个选项,但同样,我想避免它。

SOLUTIONThis solution comes from the answers by @konrad-rudolph @mark-ransom and @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). 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. A drawback - this leaks memory--- one generator per thread.

解决方案此解决方案来自 @konrad-rudolph @mark-ransom 和 @mathk 的答案。随机数生成器的播种是为了满足我的特定需求。更常见的方法是使用 time(NULL)。如果您在同一秒内创建多个线程,那么它们将获得相同的种子。即使使用 clock() 这也是一个问题,因此我们包含线程 id。一个缺点——这会泄漏内存——每个线程一个生成器。

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

回答by Konrad Rudolph

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 需要同步访问,这从长远来看成本更高)。

回答by Mike Seymour

Make the generator static, so it's only created once. 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.

制作 generator static,所以它只创建一次。这更有效,因为好的生成器通常具有较大的内部状态;更重要的是,这意味着您实际上获得的是它生成的伪随机序列,而不是单独序列的(随机性低得多)初始值。

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. The former is likely to be faster, especially if there's a lot of contention, but will consume more memory.

为了线程安全,选项是让 generatorthread_local为每个线程使用不同的种子,或者用互斥锁保护它。前者可能更快,尤其是在存在大量争用的情况下,但会消耗更多内存。

回答by mathk

You can use one default_random_engineper thread using Thread Local Storage.

您可以使用default_random_engine线程本地存储为每个线程使用一个。

I can not tell you how to correctly use TLS since it is OS dependent. The best source you can use is to search through the internet.

我无法告诉您如何正确使用 TLS,因为它依赖于操作系统。您可以使用的最佳来源是通过互联网进行搜索。

回答by imallett

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
}

回答by rossum

Write a simple LCG (or whatever) PRNG for yourself, which will produce numbers up to the maximum possible required. 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. Each thread-local PRNG will have its own local storage, and never needs to refer to the central RNG again.

为自己编写一个简单的 LCG(或其他)PRNG,这将产生尽可能大的数字。使用内置 RNG 的单个静态副本为您生成的每个新线程创建您自己的 PRNG 的新本地副本。每个线程本地 PRNG 都有自己的本地存储,永远不需要再次引用中央 RNG。

This assumes that a statistically good RNG is fine for you and that cryptographic security is not an issue.

这假设统计上良好的 RNG 对您来说很好,并且加密安全性不是问题。