web-dev-qa-db-ja.com

複数のスレッドからstdlibのRand()を使用する

同じ機能を実行する複数のスレッドがあります。これらのそれぞれで、異なる乱数を数回生成します。関数の先頭にsrand(time(0))を配置してこれを実行しようとしましたが、すべて同じ番号を取得しているようです。

srand(time(0))をプログラムごとに1回だけ呼び出す必要がありますか。つまり、mainの開始時(たとえば)、複数回呼び出される各関数の開始時などに必要ですか。

40
SIMEL

srand() 乱数ジェネレーターにシードします。起動時にsrand(time(NULL))を一度だけ呼び出す必要があります。

ただし、ドキュメントには次のように記載されています。

関数Rand()は、再入可能でもスレッドセーフでもありません。これは、呼び出しごとに変更される隠し状態を使用するためです。これは、次の呼び出しで使用されるシード値である場合もあれば、より複雑な値である場合もあります。スレッド化されたアプリケーションで再現可能な動作を得るには、この状態を明示的にする必要があります。関数Rand_r()には、状態として使用されるunsigned intへのポインターが提供されます。これは非常に少量の状態であるため、この関数は弱い擬似乱数ジェネレーターになります。代わりに drand48_r (3)を試してください。

上記の強調された部分は、おそらくすべてのスレッドが同じ数を取得する理由です。

37

スレッドをすべて同時に起動する場合、srandに送信される時間はおそらく各スレッドで同じです。それらはすべて同じシードを持っているため、すべて同じシーケンスを返します。ローカル変数のメモリアドレスなどを使用してみてください。

8
Peter Heath

Rand manページから:

関数Rand()は、各呼び出しで変更される隠し状態を使用するため、再入可能またはスレッドセーフではありません。

そのため、スレッドコードでは使用しないでください。使用する Rand_r(または drand48_r linux/glibcを使用している場合)。 Seed異なる値を持つ各RNG(メインスレッドで最初のRNGをシードして、各スレッドのシードにランダムシードを生成できます))。

7
Mat

CではなくC++を使用しているため、c ++ 11を使用することで、srand/Randによく関連するスレッドの問題を回避できる場合があります。これは、これらの機能をサポートする最新のコンパイラを使用することに依存しています。各スレッドで個別のエンジンとディストリビューションを使用します。この例はサイコロのように動作します。

#include <random>
#include <functional>

std::uniform_int_distribution<int> dice_distribution(1, 6);
std::mt19937 random_number_engine; // pseudorandom number generator
auto dice_roller = std::bind(dice_distribution, random_number_engine);
int random_roll = dice_roller();  // Generate one of the integers 1,2,3,4,5,6.

この質問に答えるとき、私は Wikipedia C++ 11 および Boost random に言及しました。

7
kshepherd

それは良い質問です。大きな問題があると思うので、直接答えることはできません。とにかく、Randがスレッドセーフであることは明らかではありません。内部状態を維持し、プロセスごとまたはスレッドごとの場合、およびスレッドセーフの場合はプロセスごとの場合は、明確に定義されていないようです。

確実に、各アクセスの周りにミューテックスをロックします。

または、できれば boost

2
jcoder

Cはマルチスレッド用に設計されていないため、マルチスレッドでのsrand()の動作は定義されておらず、Cランタイムライブラリに依存します。

多くのUnix/Linux Cランタイムライブラリは単一の静的状態を使用するため、複数のスレッドからアクセスするのは安全ではないため、これらのCランタイムでは、複数のスレッドからsrand()およびRand()を使用できません。他のUnix Cランタイムは異なる動作をする場合があります。

Visual C++ランタイムはスレッドごとの内部状態を使用するため、スレッドごとにsrand()を呼び出しても安全です。しかし、ニールが指摘したように、すべてのスレッドに同じ値をシードする可能性が高いため、代わりに(time + thread-id)をシードします。

もちろん、移植性のために、Rand関数ではなくRandomオブジェクトを使用すると、非表示状態にまったく依存しません。スレッドごとに1つのオブジェクトが必要であり、各オブジェクトに(time + thread-id)をシードすることは、まだ良い考えです。

1
Michael Entin