2011-05-06 10 views
6

He depurado algún problema con un Paint.Net plugin y me he topado con algún problema con la clase Random, cuando varios hilos llaman a un método desde una sola instancia.Problemas de concurrencia con Random en .Net?

Por alguna extraña razón, parece que si no impido el acceso simultáneo, al sincronizar el método llamado, mi instancia aleatoria comienza a comportarse ... al azar (pero en el mal sentido).

En el siguiente ejemplo, creo varios cientos de hilos que llaman repetidamente un solo objeto aleatorio. Y cuando lo ejecuto, a veces (no siempre, pero casi) obtengo resultados claramente incorrectos. El problema NUNCA ocurre si elimino el comentario de la anotación de método 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); 
     } 
    } 
} 

Un ejemplo ouput (con los valores anteriores):

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

Es esto algún problema conocido? ¿Es incorrecto llamar a un objeto aleatorio desde varios hilos sin sincronizar?

ACTUALIZACIÓN: según las respuestas, los documentos indican que los métodos aleatorios no son seguros para subprocesos - mea culpa, debería haberlo leído. Tal vez lo había leído antes, pero no creía que fuera tan importante; uno podría (descuidadamente) pensar que, en el raro caso de que dos hilos ingresen al mismo método al mismo tiempo, lo peor que podría pasar es que esas llamadas obtengan resultados incorrectos, no un gran problema, si no estamos demasiado preocupados por la calidad de los números aleatorios ... Pero el problema es realmente catastrófico, porque el objeto se deja en un estado inconsistente, y de ahí en adelante regresa a cero, como se indica en here.

+0

A menos que la documentación indique el opuesto e puede suponer que cualquier clase admite múltiples subprocesos que acceden a una sola instancia. – CodesInChaos

Respuesta

17

Por alguna extraña razón

No es realmente extraño - Random es documented no ser seguro para subprocesos.

Es un dolor, pero así es la vida. Consulte mi article on Random para obtener más información y sugerencias sobre cómo tener una instancia por subproceso, con guardias contra comenzar con la misma inicialización en varios subprocesos.

+0

ouch - un dolor, de hecho. gracias – leonbloy

+1

Ver también el artículo de Jon, https://msmvps.com/blogs/jon_skeet/archive/2009/11/04/revisiting-randomness.aspx – Brian

8

La clase Random no es segura para subprocesos.

Desde el docs:

Any instance members are not guaranteed to be thread safe 

En lugar de sincronización que hará que todos los hilos para bloquear, intente implementar el atributo ThreadStatic.

+0

Gracias. Todavía estoy sorprendido de que el problema parezca tan catastrófico. Hubiera esperado que un par de valores aleatorios estuvieran "equivocados" para un par de llamadas simultáneas, pero parece que el objeto aleatorio está totalmente jodido, y desde allí vuelve disparates. – leonbloy

1

Desafortunadamente esto es correcto, hay que tener cuidado cuando se usa la clase aleatoria.

Aquí hay dos entradas de blog con más detalles, comentarios y ejemplos de código sobre este tema:

La peor parte de este comportamiento es que simplemente deja de funcionar (es decir, una vez que se produce el problema, el valor de retorno de los métodos 'random.Next ....' es 0)

+0

"que simplemente deja de funcionar" ¿Por qué es esa la peor parte? IMO es la mejor parte. De lo contrario, no notarías que tu código está roto. – CodesInChaos

+0

"Tengo que decir que tengo la sensación de que hay una serie de vulnerabilidades de seguridad creadas por este comportamiento". No provienen de la falta de seguridad de subprocesos, sino del uso de 'Random' en primer lugar. Debería usar un crypto rng para todas las cosas relacionadas con la seguridad. – CodesInChaos

Cuestiones relacionadas