2011-12-14 46 views
0

我很努力地找到一種方法,讓每個線程有一個隨機數生成器,同時確保重新運行程序時生成相同的數字。用已知種子創建ThreadLocal隨機生成器

我現在做的是這樣的:

class Program { 
    static void Main(string[] args) { 

     var seed = 10; 
     var data = new List<double>(); 
     var dataGenerator = new Random(seed); 

     for (int i = 0; i < 10000; i++) { 
      data.Add(dataGenerator.NextDouble()); 
     } 

     var results = new ConcurrentBag<double>(); 

     Parallel.ForEach(data, (d) => { 
      var result = Calculate(d, new Random(d.GetHashCode()); 
      results.Add(result); 
     }); 

    } 

    static double Calculate(double x, Random random) { 
     return x * random.NextDouble(); 
    } 
} 

因爲創建了「數據」列表中的randomgenerator提供種子以及基於被提供的種子是在計算中使用的randomgenerators正在處理的數字的哈希碼,結果是可重複的。無論線程的數量和實例化的順序如何。

我想知道是否有可能爲每個線程實例化一個隨機生成器。下面的代碼片段似乎可以實現這一點,但由於隨機生成器不再提供(可再生)種子,結果不可重複。

class Program { 
    static void Main(string[] args) { 

     var seed = 10; 
     var data = new List<double>(); 
     var dataGenerator = new Random(seed); 

     for (int i = 0; i < 10000; i++) { 
      data.Add(dataGenerator.NextDouble()); 
     } 

     var results = new ConcurrentBag<double>(); 

     var localRandom = new ThreadLocal<Random>(() => new Random()); 

     Parallel.ForEach(data, (d) => { 
      var result = Calculate(d, localRandom.Value); 
      results.Add(result); 
     }); 

    } 

    static double Calculate(double x, Random random) { 
     return x * random.NextDouble(); 
    } 
} 

任何人都可以想出一個很好的解決這個問題?

+0

我不認爲這是可能的。第一種解決方案的工作原理是因爲你的隨機數生成器與線程數無關。在第二種情況下,當您爲每個線程創建一個隨機生成器時,即使您找到一致的方式來初始化種子,結果也將取決於線程的數量。也許做一個Random的自定義實現,讓你每次生成一個新的數字時都提供一個種子? – 2011-12-14 09:43:31

+0

無賴。我認爲每次設置一個新的種子就像每次實例化一個新的隨機一樣。 – jkokorian 2011-12-14 11:08:21

回答

3

這是可能的,你的問題的確可以做得很準確,但問題在於那不是你想要的。

如果你每次用種類相同的號碼爲你的線程本地Random播種,那麼你會在該線程中確定結果,這與前面的操作數有關。你想要的是一個僞隨機數,相對於輸入是確定的。

那麼,你可以堅持Random()。這並不重要。

或者,您可以擁有自己的僞隨機算法。下面是基於重新散列算法(旨在更好地分配散列碼的位)一個簡單的例子:

private static double Calculate(double x) 
{ 
    unchecked 
    { 
    uint h = (uint)x.GetHashCode(); 
    h += (h << 15)^0xffffcd7d; 
    h ^= (h >> 10); 
    h += (h << 3); 
    h ^= (h >> 6); 
    h += (h << 2) + (h << 14); 
    return (h^(h >> 16))/(double)uint.MaxValue * x; 
    } 
} 

這是不是一個特別好的僞隨機生成的,但它是相當快的。它也沒有分配,導致沒有垃圾收集。

這裏存在着這種整個方法的權衡;你可以簡化上述步驟,甚至可以更快但更少的「隨機」,或者你可以更「隨機」的更多努力。我敢肯定,那裏的代碼比上面的代碼更快,更「隨機」,這更多的是爲了展示這種方法,而不是其他任何方法,但是在你考慮的競爭對手算法中,生成的數量與性能。 new Random(d).NextDouble()在這種折衷中處於特殊的地位,其他方法在其他方面。

編輯:我使用的重新哈希算法是一個王/詹金斯哈希。當我寫下這個名字時,我不記得這個名字。

編輯:具有從評論你的要求一個更好的主意,我現在說......

你想創建一個PRNG類,它可以使用上面的算法,即中System.Random(以反射代碼作爲起點),您提到的128bitXorShift算法或其他。重要的區別是它必須有一個Reseed方法。例如,如果你複製了System.Random的方法,你的種子將看起來像構造函數的大部分體(事實上,你可能會重構,所以除了可能創建它使用的數組之外,構造函數會調用重新設置)。

然後,您會爲每個線程創建一個實例,並在您現有代碼中創建新的Random時調用.Reseed(d.GetHashCode())

還要注意,這給了你另一個優點,即如果你依賴於你的PRNG的一致結果(看起來你是這麼做的),那麼你不承諾在框架版本之間的System.Random中有一致的算法甚至可能包括修補程序和安全修復程序)對您而言是一個壞點,並且此方法會增加一致性。

但是,你也沒有答應一致的算法double.GetHashCode()。我懷疑他們會改變它(不像string.GetHashCode(),這是經常發生變化),但以防萬一,你可以讓你的Reseed()採取雙重做這樣的事情:

private static unsafe int GetSeedInteger(double d) 
{ 
    if(d == 0.0) 
    return 0; 
    long num = *((long*)&d); 
    return ((int)num)^(int)(num >> 32); 
} 

這非常簡單,只是將當前double.GetHashCode() ,但現在你將面對框架變化時保持一致。

這可能是值得考慮打破了一組任務分成塊自己,因爲每個塊創建線程,然後只需在每塊方法創建該對象爲本地。

優點:

訪問ThreadLocal<T>比訪問本地T更加昂貴。

如果任務是在相對時間執行一致的,你不需要很多Parallel.ForEach的聰明。

缺點:

Parallel.ForEach是在平衡東西出來真的很好。你在做的事情必須非常自然地平衡,或者在避開它的使用獲得任何東西之前,在前塊基礎上節省很多。