スレッドと乱数

 

 

 

このページの情報は、私のパソコン利用環境で、少なくとも1度は実行などしたものを記載していますが、

このページの情報を利用して損害などが生じても、私(このWEBページの作成者、programtips)は一切責任は負いません。

またVisualStudio2015のインストールされた64ビット版Windows10パソコンを前提にしています。

 

 1.スレッド中における乱数

IMEとは別に、VisualStudio2015でVC++のプログラムで、

スレッドを生成して乱数を利用しようとしたのですが、

なかなかうまく乱数を発生させられませんでした。

その現象についての検討をしてみます。

なぜ突然この記事なのかというと、問題が独立していて書きやすいからです。

 

2.素朴な乱数生成

まず単純に以下のような方法はどうでしょう。

(WINDOWS10+VisualStudio2015であれば、VC++の空のプログラムでプロジェクトを作成し、ソースファイルを追加して以下のプログラムをコピーしてすぐ実行できると思います。

ほかの環境でもそれほど改変せずに実行できるのではないかと思います。以下、すべてのプログラムについて同様)

#include <iostream>
#include<windows.h>
#include<process.h>
#include<time.h>
using namespace std;
 
HANDLE hEO;
 
void setRandom(void * temp)
{
    for (int i = 0; i < 6; i++)
    {
        cout << i << ":" << rand() <<" ";
    }
    SetEvent(hEO);
    return;
}
 
int main()
{
    hEO = CreateEvent(NULL, false, false, NULL);
    for (int i = 0; i < 6; i++)
    {
        cout << i << ". main: " << rand();
        cout << "    partial: ";
 
        ResetEvent(hEO);
        _beginthread(setRandom, 0, 0);
        WaitForSingleObject(hEO, INFINITE);
 
        cout << endl;
    }
    int j;
    cin >>j;
}

  

これは単純に、_beginthreadで、スレッドを生成して、生成したスレッドでsetRandom関数を実行、そしてrand()関数で、乱数の生成を行ない、元スレッドのほうで生成した乱数と一緒に表示しています。

SetEvent(hEO);、hEO = CreateEvent(NULL, false, false, NULL);、  ResetEvent(hEO);、        WaitForSingleObject(hEO, INFINITE);は、元のスレッドが先に進まないようにするもので、

生成したスレッドの処理が終了するまで次の行に進まないようにしています。

このプログラムを実行すると以下のようになります。表示後、英数字キーをおしてEnterキーを押せば終了します。

このようになってしまい、元のスレッドと生成させたスレッドの乱数は毎回同じ数字が繰り返されてしまいます。

この場合MSDNのサイトを見るのがいいかはわかりませんが、

以下のsrand(randではない)の説明を見ると、「現在のスレッド」に乱数生成の開始点を設定するとあるので、

おそらくrandによる疑似乱数発生はスレッドごとになっていると考えられます。

MSDNのsrandの説明

  

このほうが都合がよい場合もあるかもしれませんが、さしあたり、これは希望した結果ではないので、

工夫する必要があります。

 

3.乱数生成のいくつかのバリエーション

まず、上ででてきたsrandを利用して乱数を初期化する方法が考えられます。

#include <iostream>
#include<windows.h>
#include<process.h>
#include<time.h>
using namespace std;
 
HANDLE hEO;
 
void setRandom(void * temp)
{
    srand((unsigned)time(NULL));
    for (int i = 0; i < 6; i++)
    {
        cout << i << ":" << rand() << " ";
    }
    SetEvent(hEO);
    return;
}
 
int main()
{
    hEO = CreateEvent(NULL, false, false, NULL);
    for (int i = 0; i < 6; i++)
    {
        cout << i << ". main: " << rand();
        cout << "    partial: ";
 
        ResetEvent(hEO);
        _beginthread(setRandom, 0, 0);
        WaitForSingleObject(hEO, INFINITE);
 
        cout << endl;
    }
    int j;
    cin >> j;
}

  

さてここで、生成したスレッドでsrandを実行して乱数発生を初期化してみます。

これを実行すると以下のようになります。

今回は、本体のスレッドと、生成したスレッドは別になりました。

しかし、生成したスレッドの値はまだ全部一緒のままです。

これは、プログラムの実行が早すぎて、乱数の元にしているtime()の値が変化していないからだと考えられます。

そこで、Sleep関数で少し実行を遅くしてみたサンプルもありますが、それは省略して、

setRandomが実行されるたびに、乱数の元に、ある程度の数を加えるようにしてみます。

#include <iostream>
#include<windows.h>
#include<process.h>
#include<time.h>
using namespace std;
 
HANDLE hEO;
int rseed = 10000;
 
void setRandom(void * temp)
{
    rseed += 10000;
    srand((unsigned)time(NULL)+rseed);
    for (int i = 0; i < 6; i++)
    {
        cout << i << ":" << rand() << " ";
    }
    SetEvent(hEO);
    return;
}
 
int main()
{
    hEO = CreateEvent(NULL, false, false, NULL);
    for (int i = 0; i < 6; i++)
    {
        cout << i << ". main: " << rand();
        cout << "    partial: ";
        ResetEvent(hEO);
        _beginthread(setRandom, 0, 0);
        WaitForSingleObject(hEO, INFINITE);
 
        cout << endl;
    }
    int j;
    cin >> j;
}

  

今回はrseedという変数を作って、

setRandomが呼ばれるたびに、rseedを10000ずつ増やして、これを乱数の元に加えています。

これを実行すると以下のようになります。

今回は、全部の数字がばらばらになりました。

しかし、まだ、似たような数字がならんでいます。

そのため、この方法で発生させた乱数を、〇 ×の生成に利用する以下のような方法では、

#include <iostream>
#include<windows.h>
#include<process.h>
#include<time.h>
using namespace std;

HANDLE hEO;
int rseed = 10000;

void setRandom(void * temp)
{
    rseed += 10000;
    srand((unsigned)time(NULL) + rseed);
    for (int i = 0; i < 6; i++)
    {
        int randVal = rand();
        cout << i << ":" << randVal << "";
        if (randVal * 2 / RAND_MAX >= 1)
        {
            cout << " ○  ";
        }
        else
        {
            cout << " ×  ";
        }
    }
    SetEvent(hEO);
    return;
}
 
int main()
{
    hEO = CreateEvent(NULL, false, false, NULL);
    for (int i = 0; i < 6; i++)
    {
        cout << i << ". main: " << rand();
        cout << "    partial: ";
        ResetEvent(hEO);
        _beginthread(setRandom, 0, 0);
        WaitForSingleObject(hEO, INFINITE);
 
        cout << endl;
    }
    int j;
    cin >> j;
}

次のようにかなりの偏りが生じます。(RAND_MAXはrandで発生する乱数の最大値と考えてよいと思います)

  

4.まとめ

というわけで、いくつか乱数の発生方法と、その結果を見てみましたが、

単なる乱数といえども、なかなか難しい場合があるように思います。

実際に作っていたプログラムでは、乱数発生専用のスレッドを作成して、そこで乱数を発生させるようにしましたが、

その方法にも、ここで見たような方法にも、偏りがないとは言えないということは言えると思います。あまり乱数を信用しないほうがいいというように思います。

また、意図的に乱数を操作しても、利用者側はよくわからなくてなかなかそういう操作が発覚しにくいというのもあります。

本主題とは、ずれますが、マイクロソフトとかが、いつの間にかアップデートしたりすると、いつの間にか動作が変わってたりする危険もあります。

さすがにマイクロソフト外部の言語の仕様に依拠するところはあんまり変えないとは思いますが。

 

マルチスレッドの関数などについては以下のページなども参考にしてください。

猫でもわかるプログラミングWindows SDK編第89章

 

 

 

トップページに戻る

 

ご意見、ご質問などは下記掲示板にお願いします。

 掲示板

 

作成2016/09/25

(c)2016  programtips

inserted by FC2 system