c++ - 为什么这个随机数生成器不是线程安全的?
问题描述
我使用rand()
函数来生成 0,1 之间的伪随机数以用于模拟目的,但是当我决定让我的 C++ 代码并行运行(通过 OpenMP)时,我注意到rand()
它不是线程安全的,也不是很统一。
所以我转而使用在其他问题的许多答案中出现的(所谓的)更统一的生成器。看起来像这样
double rnd(const double & min, const double & max) {
static thread_local mt19937* generator = nullptr;
if (!generator) generator = new mt19937(clock() + omp_get_thread_num());
uniform_real_distribution<double> distribution(min, max);
return fabs(distribution(*generator));
}
但是我在模拟的原始问题中看到了许多科学错误。既违背常识的结果rand()
,也违背常识的问题。
所以我写了一个代码,用这个函数生成 500k 个随机数,计算它们的平均值,然后重复 200 次并绘制结果。
double SUM=0;
for(r=0; r<=10; r+=0.05){
#pragma omp parallel for ordered schedule(static)
for(w=1; w<=500000; w++){
double a;
a=rnd(0,1);
SUM=SUM+a;
}
SUM=SUM/w_max;
ft<<r<<'\t'<<SUM<<'\n';
SUM=0;
}
我们知道,如果不是 500k 我可以无限次执行它,它应该是一条值为 0.5 的简单线。但是对于 500k,我们将有 0.5 左右的波动。
使用单线程运行代码时,结果是可以接受的:
但这是 2 个线程的结果:
3个线程:
4个线程:
我现在没有我的 8 线程 CPU,但结果是值得的。
如您所见,它们都不统一,并且在平均值附近波动很大。
那么这个伪随机生成器也是线程不安全的吗?
还是我在某个地方犯了错误?
解决方案
我将对您的测试输出进行三个观察:
它的方差比一个好的随机源的平均值应该提供的要强得多。您通过与单线程结果进行比较自己观察到了这一点。
计算出的平均值随着线程数的增加而减少,并且永远不会达到原来的 0.5(即它不仅是更高的方差,而且还改变了平均值)。
数据中存在时间关系,在 4 线程情况下尤其明显。
所有这些都可以通过代码中存在的竞争条件来解释:您SUM
从多个线程中分配。增加双精度不是原子操作(即使在 x86 上,您可能会在寄存器上进行原子读取和写入)。两个线程可以读取当前值(例如 10),增加它(例如都加 0.5),然后将值写回内存。现在您有两个线程写入 10.5 而不是正确的 11。
尝试同时写入的线程越多SUM
(不同步),它们的更改丢失的越多。这解释了所有观察结果:
线程在每次单独运行中相互竞争的强度决定了丢失了多少结果,这可能因运行而异。
比赛越多(例如线程越多),平均值越低,因为丢失的结果越多。您永远无法超过统计平均值 0.5,因为您只会丢失写入。
随着线程和调度程序“安顿下来”,差异会减少。这与为什么在基准测试时应该“预热”测试的原因类似。
不用说,这是未定义的行为。它只是在 x86 CPU 上显示良性行为,但这不是 C++ 标准向您保证的。如您所知,双精度的各个字节可能会同时被不同的线程写入,从而导致完全垃圾。
正确的解决方案是在本地添加双打线程,然后(通过同步)最后将它们添加在一起。OMP 有针对此特定目的的减少条款。
对于整数类型,您可以使用std::atomic<IntegralType>::fetch_add()
. std::atomic<double>
存在,但(在 C++20 之前)提到的函数(和其他)仅可用于整数类型。
推荐阅读
- codeigniter - 停止 CodeIgniter 模型以格式化 SQL 日期字段
- mysql - where子句中的MySQL未知列(UNION)
- c++ - 使用 QtCreator 部署自定义构建项目
- java - 即使列表不为空,emptyView 也始终显示
- c# - 使用 EWS 托管 API (Exchange 2010) 访问共享联系人
- python - 使用 tf.data 读取 CSV 文件非常慢,使用 tfrecords 代替?
- javascript - 使用 css 转换 div
- ios - SwiftSpinner 在调用 API 时遇到问题 - swift ios
- xamarin.forms - Xamarin.forms 中的插件与依赖项服务
- mysql - 替代最大查询?