1. 程式人生 > >多執行緒中生成隨機數序列重複問題的解決方法

多執行緒中生成隨機數序列重複問題的解決方法

使用過隨機數的程式設計師都知道在程式中並不能夠實現的真正的完全的隨機數函式。隨機數函式產生的是通過公式計算出來的一系列偽隨機數,這個公式會採用一個種子數計算出一個數,而該數將成為產生下一個數的種子數。基於產生隨機數的原理,兩次呼叫隨機數後產生的隨機數序列將是一樣的,顯然,這不是我們的期望的結果。

為了解決上述問題,使得隨機數函式產生的數儘量隨機,程式語言通常提供了設定種子數的功能,而一般情況下,程式設計師會使用當前時間作為種子數,這樣兩次呼叫隨機函式產生的隨機數序列就會不同,進而達到更加隨機的效果。

對於C/C++而言,通常產生的隨機數的方法是呼叫以下兩個函式:

srand(time(NULL));

// 設定隨機數種子, 引數內是使用當前時間作為種子

rand();// 產生一個隨機數

由於一般情況下呼叫srand(time(NULL))的時間不會一樣,所以每次產生的隨機數序列也會不同,因而可以達到近似完全隨機的效果。但是,如果是在多執行緒的環境下需要使用隨機數,情況將變得有所不同。

以下是一個線上程中生成隨機數的示例,為了能將生成的隨機數顯示出來,測試程式肺採用了MFC對話方塊,並將視窗控制代碼傳入到了執行緒中,當執行緒中產生了一個隨機數後,以發訊息的形式通知對話方塊顯示產生出來的隨機數。

/* 產生隨機數的執行緒*/

UINT RandThread(LPVOID p)

{

if(NULL == p)

return 1;

PTHREADPARA pt = (PTHREADPARA)p;

// 初始化隨機種子

::srand(time(NULL));

// 生成隨機數,並通過訊息顯示在介面上

int nRnd;

for(int i = 1; i <= 20; i++)

{

nRnd = ::rand();

PostMessage(pt->hWind, WM_USER_RAND, pt->nThreadFlag, nRnd);

}

// 釋放執行緒引數空間

if(pt)

{

delete pt;

pt = NULL;

}

return 0;

}

在測試程式中,將上述執行緒啟動5次,可以發現,得到的隨機數序列是完全一樣的。下圖是其中一次執行的結果:

 執行緒生成相同的隨機數序列

分析其原因,可能是因為5個執行緒啟動的時間相差無幾,而不同執行緒呼叫rand()函式時相對是獨立的,因而產生的隨機數序列也就是相同的。

實際上time(NULL)所取得的時間只能精確到秒,而啟動5個執行緒如果在同一秒內完成(經測試,在1.6G單核機器上測試時一秒內啟動10個執行緒也沒有問題,能在同一秒內啟動的執行緒數量與機器當前繁忙程度、CUP效能等有很大關係),就必然形成上述生成同一隨機數序列的現象。

通過上述分析,如果能令不同執行緒設定的隨機數種子不同,就應該可以令不同執行緒產生不同的隨機數序列。一個容易想到的辦法是在相隔較長的時間裡啟動不同的執行緒,但是如果要求所有執行緒在較短的時間內連續啟動呢?那我們就只能尋找可以設定不同種子數的辦法。

既然time(NULL)的精度只能到秒級,那麼我們可以嘗試使用時間精度更高的函式來獲得一個種子數。

配合使用結構體timeb與函式ftime可以取得毫秒級的時間,它應當可以使得多執行緒生成同一序列隨機數的現狀有所改觀。修改初始化種子數的程式碼如下:

struct timeb stb;

ftime(&stb);

srand((unsigned)stb.millitm);)

重新執行測試程式,可以發現以毫秒級的時間作為種子數依然感覺不夠用,部分執行緒產生的隨機數序列依然相同,也就是說在同一毫秒內也可以啟動兩個以下的執行緒。那麼我們只能繼續尋找精度更高的與時間相關的函式。

經查詢資料,函式QueryPerformanceCounter可以返回高精度的計數器值,其精度可達微秒級。通常該函式會與函式QueryPerformanceFrequency配合使用,函式QueryPerformanceFrequency的功能是如果當前機器存在定時器則查詢出當前機器定時器的頻率,我們可以利用QueryPerformanceFrequency測試當前系統裡是否有這個高精度的定時器,如果有,則可以呼叫QueryPerformanceCounter獲得一個精度很高的計數值。

利用QueryPerformanceFrequencyQueryPerformanceCounter設定種子數的程式碼如下:

if(::QueryPerformanceFrequency(&nFrequency))

{

LARGE_INTEGER nStartCounter;

::QueryPerformanceCounter(&nStartCounter);

::srand((unsigned)nStartCounter.LowPart);

}

再次執行測試程式,可以發現,每個執行緒產生的隨機數序列已經完全不一樣了。下圖是其中一次執行的結果:

 執行緒生成不同的隨機數序列

以下列出完整的執行緒程式碼,供參考。

/* 初始化隨機種子 */

void InitRand()

{

// 如果支援高效能精度計數器,則使用其初始化隨機種子(微秒級)

LARGE_INTEGER nFrequency;

if(::QueryPerformanceFrequency(&nFrequency))

{

LARGE_INTEGER nStartCounter;

::QueryPerformanceCounter(&nStartCounter);

::srand((unsigned)nStartCounter.LowPart);

}

else // 否則使用當前系統時間初始化隨機種子(毫秒級)

{

struct timeb stb;

::ftime(&stb);

::srand((unsigned)stb.millitm);

}

}

/* 產生隨機數的執行緒*/

UINT RandThread(LPVOID p)

{

if(NULL == p)

return 1;

PTHREADPARA pt = (PTHREADPARA)p;

// 初始化隨機種子

//::srand(time(NULL));

InitRand();

// 生成隨機數,並通過訊息顯示在介面上

int nRnd;

for(int i = 1; i <= 20; i++)

{

nRnd = ::rand();

PostMessage(pt->hWind, WM_USER_RAND, pt->nThreadFlag, nRnd);

}

// 釋放執行緒引數空間

if(pt)

{

delete pt;

pt = NULL;

}

return 0;

}

如果需要測試程式完整程式碼,可以通過QQ聯絡我。