2011-05-06 65 views
6

我調試了Paint.Net plugin的一些問題,當幾個線程從一個實例調用一個方法時,我偶然發現了Random類的一些問題。隨機在.NET中的併發問題?

由於一些奇怪的原因,似乎如果我不阻止併發訪問,通過同步被調用的方法,我的隨機實例開始行爲......隨機(但意義不大)。

在下面的例子中,我創建了數百個線程,它們重複調用一個Random對象。當我運行它時,我有時(並不總是,但幾乎)得到明顯錯誤的結果。如果我取消註釋Synchronized方法註釋,則不會發生此問題。

using System; 
using System.Threading; 
using System.Runtime.CompilerServices; 
namespace testRandom { 

    class RandTest { 
     static int NTIMES = 300; 

     private long ac=0; 

     public void run() { // ask for random number 'ntimes' and accumulate 
      for(int i=0;i<NTIMES;i++) { 
       ac+=Program.getRandInt(); 
       System.Threading.Thread.Sleep(2); 
      } 
     } 

     public double getAv() { 
      return ac/(double)NTIMES; // average 
     } 
    } 

    class Program 
    { 

     static Random random = new Random(); 
     static int MAXVAL = 256; 
     static int NTREADS = 200; 

     //[MethodImpl(MethodImplOptions.Synchronized)] 
     public static int getRandInt() { 
      return random.Next(MAXVAL+1); // returns a value between 0 and MAXVAL (inclusive) 
     } 

     public static void Main(string[] args)  { 
      RandTest[] tests = new RandTest[NTREADS]; 
      Thread[] threads = new Thread[NTREADS]; 
      for(int i=0;i<NTREADS;i++) { 
       tests[i]= new RandTest(); 
       threads[i] = new Thread(new ThreadStart(tests[i].run)); 
      } 
      for(int i=0;i<NTREADS;i++) threads[i].Start(); 
      threads[0].Join(); 
      bool alive=true; 
      while(alive) { // make sure threads are finished 
       alive = false; 
       for(int i=0;i<NTREADS;i++) { if(threads[i].IsAlive) alive=true; } 
      } 
      double av=0; 
      for(int i=0;i<NTREADS;i++) av += tests[i].getAv(); 
      av /= NTREADS; 
      Console.WriteLine("Average:{0, 6:f2} Expected:{1, 6:f2}",av,MAXVAL/2.0); 

      Console.Write("Press any key to continue . . . "); 
      Console.ReadKey(true); 
     } 
    } 
} 

一個例子輸出中(與上面的值):

Average: 78.98 Expected:128.00 
Press any key to continue . . . 

這是一些已知的問題?從多個線程中調用Random對象而不同步是不正確的?

更新:根據答案,文檔指出隨機方法不是線程安全的 - mea culpa,我應該讀取它。也許我之前已經閱讀過這篇文章,但並不認爲它如此重要 - 人們可能會認爲,在兩個線程同時進入相同方法的情況下,最糟糕的情況是這些調用會得到錯誤的結果 - 而不是一個巨大的交易,如果我們不太關心隨機數的質量......但這個問題真的是災難性的,因爲對象處於不一致的狀態,並且從這個狀態返回返回爲零 - 如註釋here

+0

除非文檔聲明相反你可以假定任何類都支持訪問單個實例的多個線程。 – CodesInChaos 2011-07-16 07:55:26

回答

17

一些奇怪的原因

這不是真的很奇怪 - Randomdocumented不是線程安全的。

這是一種痛苦,但那就是生活。有關更多信息,請參閱我的article on Random,以及有關如何爲每個線程創建實例的建議,以及防止以多個線程中的相同種子開始的警告。

+0

哎 - 確實是一種痛苦。謝謝 – leonbloy 2011-05-06 18:23:15

+1

另請參閱Jon的文章https://msmvps.com/blogs/jon_skeet/archive/2009/11/04/revisiting-randomness.aspx – Brian 2011-05-06 21:10:50

8

Random類不是線程安全的。

docs

Any instance members are not guaranteed to be thread safe 

取而代之的同步,這將導致所有的線程阻塞,儘量實現ThreadStatic屬性。

+0

謝謝。我仍然驚訝於這個問題看起來如此災難 - 我希望某些隨機值對於某對併發調用是「錯誤的」,但似乎隨機對象完全被搞砸了,而且從它返回廢話。 – leonbloy 2011-05-06 18:29:47

1

不幸的是,這是正確的,使用隨機類時必須小心。

這裏有更多的細節,註釋和代碼示例對這個議題二的博客文章:

這種行爲的更糟糕的是,它只是停止工作(即,一旦發生問題,來自'random.Next ....'方法的返回值爲0)

+0

「它只是停止工作」爲什麼這是最糟糕的部分?海事組織是最好的部分。否則你不會注意到你的代碼被破壞了。 – CodesInChaos 2011-07-16 07:57:56

+0

「我不得不說,我有這樣的感覺,那就是由於這種行爲而產生了許多安全漏洞。」這些並不是來自於缺乏線程安全性,而是來自於首先使用「Random」。你應該對所有安全相關的東西使用加密的rng。 – CodesInChaos 2011-07-16 07:59:36