這是在玩三門問題時程式沒寫好遇到的狀況,做個筆記。

以下程式會連續建立1000個Test物件,Test物件建構式中會產生A, B, C三個隨機亂數。大家有發現其中存在什麼問題嗎?

using System;
 
namespace TestRandom
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 1000; i++)
            {
                Test t = new Test();
                Console.WriteLine(
                    "NO:{0:000} A:{1:00} B:{2:00} C:{3:00}",
                    i, t.A, t.B, t.C);
            }
            Console.Read();
        }
    }
    public class Test
    {
        public int A, B, C;
        Random rnd = new Random();
        public Test()
        {
            A = rnd.Next(100);
            B = rnd.Next(100);
            C = rnd.Next(100);
        }
    }
}

實地執行一下程式,你會發現亂數怎麼一點都不亂,例如以下結果:

NO:978 A:66 B:39 C:77
NO:979 A:66 B:39 C:77
NO:980 A:66 B:39 C:77
NO:981 A:66 B:39 C:77
NO:982 A:66 B:39 C:77
NO:983 A:66 B:39 C:77
NO:984 A:66 B:39 C:77
NO:985 A:66 B:39 C:77
NO:986 A:49 B:79 C:25
NO:987 A:49 B:79 C:25
NO:988 A:49 B:79 C:25
NO:989 A:49 B:79 C:25
NO:990 A:49 B:79 C:25
NO:991 A:49 B:79 C:25
NO:992 A:49 B:79 C:25
NO:993 A:49 B:79 C:25
NO:994 A:49 B:79 C:25
NO:995 A:49 B:79 C:25
NO:996 A:49 B:79 C:25
NO:997 A:49 B:79 C:25
NO:998 A:49 B:79 C:25
NO:999 A:49 B:79 C:25

我們會得到連續N組A, B, C亂數相同的結果,一陣子後變成另外一組亂數再重複N次,再隔一陣子後再換成另一組亂數重複N次... 問題出在亂數種子! MSDN中有詳細說明:

亂數的產生始於種子值。如果重複使用相同的種子會產生相同的連續數字。其中一個產生不同序列的方法是讓種子值時間相依,由此以每個 Random 的新執行個體 (Instance) 產生不同的系列。根據預設,Random 類別的無參數建構函式會使用系統時鐘來產生其種子值,而參數化的建構函式可以根據目前時間的刻度數目而接受 Int32 值。然而,因為時鐘的解析度有限,所以若使用無參數的建構函式在極短時間內連續建立不同的 Random 物件,就會建立亂數產生器,這些產生器會產生序列完全相同的亂數。

所以祕訣在於:

不要快速地連續new Random(),應該建立一個Random後重複使用。

以先前程式為例,最簡單的修改方式是把rnd宣告成靜態變數,全部的Test物件共用一份,就能解決問題囉!

    public class Test
    {
        public int A, B, C;
        static Random rnd = new Random();
        public Test()
        {
            A = rnd.Next(100);
            B = rnd.Next(100);
            C = rnd.Next(100);
        }
    }

Comments

# by Jeff Yeh

用蹂躪的這招不錯,用GUID當種子~ http://www.dotblogs.com.tw/larrynung/archive/2010/01/04/12801.aspx

# by Jeffrey

to Jeff Yeh, 哈! 我最先想到的也是用Guid當種子,後來覺得一直重複建立Guid(),轉換成int,再拿來建立Random()有點"搞剛"。後來發現在我的案例中,將Random物件宣告成靜態後重複使用應該更有效率。 不過,如果想徹底切斷亂數間的關聯性,用Guid()應是不錯的選擇。

# by Wayne

使用 lock(rnd) {...} 會不會比較好?

# by Jeffrey

to Wayne, 在本例中沒有啟動多執行緒,我想應該用不上lock,還是你另有其他點子? 願聞其詳~~ :P

# by WORM

這因該會比較亂 http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.rngcryptoserviceprovider(VS.80).aspx

Post a comment