2012-06-12 17 views
6

Estaba leyendo la publicación tips and tricks y pensé que probaría algunas de las cosas C# que nunca antes había hecho. Por lo tanto, el siguiente código no sirve para nada, pero es solo una "función de prueba" para ver qué sucede.C# ThreadStatic + miembros volátiles que no funcionan como se esperaba

De todos modos, tengo dos campos privados estáticas:

private static volatile string staticVolatileTestString = ""; 
[ThreadStatic] 
private static int threadInt = 0; 

Como se puede ver, estoy probando ThreadStaticAttribute y la volátil palabra clave.

De todos modos, tengo un método de prueba que tiene este aspecto:

private static string TestThreadStatic() { 
    // Firstly I'm creating 10 threads (DEFAULT_TEST_SIZE is 10) and starting them all with an anonymous method 
    List<Thread> startedThreads = new List<Thread>(); 
    for (int i = 0; i < DEFAULT_TEST_SIZE; ++i) { 
     Thread t = new Thread(delegate(object o) { 
      // The anon method sets a newValue for threadInt and prints the new value to the volatile test string, then waits between 1 and 10 seconds, then prints the value for threadInt to the volatile test string again to confirm that no other thread has changed it 
      int newVal = randomNumberGenerator.Next(10, 100); 
      staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
      threadInt = newVal; 
      Thread.Sleep(randomNumberGenerator.Next(1000, 10000)); 
      staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " finished: " + threadInt; 
     }); 
     t.Start(i); 
     startedThreads.Add(t); 
    } 

    foreach (Thread th in startedThreads) th.Join(); 

    return staticVolatileTestString; 
} 

lo que cabe esperar para ver regresar de esta función es una salida como esta:

thread 0 setting threadInt to 88 
thread 1 setting threadInt to 97 
thread 2 setting threadInt to 11 
thread 3 setting threadInt to 84 
thread 4 setting threadInt to 67 
thread 5 setting threadInt to 46 
thread 6 setting threadInt to 94 
thread 7 setting threadInt to 60 
thread 8 setting threadInt to 11 
thread 9 setting threadInt to 81 
thread 5 finished: 46 
thread 2 finished: 11 
thread 4 finished: 67 
thread 3 finished: 84 
thread 9 finished: 81 
thread 6 finished: 94 
thread 7 finished: 60 
thread 1 finished: 97 
thread 8 finished: 11 
thread 0 finished: 88 

Sin embargo, lo Me sale esto:

thread 0 setting threadInt to 88 
thread 4 setting threadInt to 67 
thread 6 setting threadInt to 94 
thread 7 setting threadInt to 60 
thread 8 setting threadInt to 11 
thread 9 setting threadInt to 81 
thread 5 finished: 46 
thread 2 finished: 11 
thread 4 finished: 67 
thread 3 finished: 84 
thread 9 finished: 81 
thread 6 finished: 94 
thread 7 finished: 60 
thread 1 finished: 97 
thread 8 finished: 11 
thread 0 finished: 88 

La segunda "mitad" de la salida es la esperada (lo que supongo significa th en el campo ThreadStatic funciona como pensé), pero parece que algunas de las salidas iniciales se han "omitido" desde la primera "mitad".

Además, los hilos en la primera 'mitad' están fuera de servicio, pero entiendo que un hilo no se ejecuta inmediatamente tan pronto como llame a Start(); pero en su lugar, los controles internos del sistema operativo iniciarán los hilos como lo considere oportuno. EDIT: No, no es, en realidad, yo sólo pensaba que eran porque mi cerebro no alcanza los números consecutivos


lo tanto, mi pregunta es: ¿Qué está pasando mal causar que pierda unas pocas líneas en el primera 'mitad' de la salida? Por ejemplo, ¿dónde está la línea 'thread 3 setting threadInt to 84'?

+0

Ejecuto su código varias veces y siempre obtengo la salida esperada ... –

+0

Apostaría que en algún lugar de la línea, la llamada a + = en la cadena obtiene el valor antes de que el hilo anterior lo haya configurado y el nuevo subproceso lo sobrescribe con el nuevo valor (por lo tanto, el subproceso 1 tuvo su salida omitida). – Charleh

+0

@DannyChen Estoy en un quad-core, y ese no es el único código que se ejecuta en la aplicación; no sé si alguno de ellos tendría sentido. Puedo cargar todo el código en algún lugar (son solo dos archivos .cs) si lo desea. – Xenoprimate

Respuesta

7

Los subprocesos se están ejecutando al mismo tiempo. Lo que conceptualmente sucede es lo siguiente:

staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
  1. Tema 1 lee staticVolatileTestString
  2. Tema 2 lee staticVolatileTestString
  3. Tema 3 lee staticVolatileTestString
  4. Tema 1 anexa la materia y escribe staticVolatileTestString volver
  5. Tema 2 agrega las cosas y escribe staticVolatileTestString atrás
  6. Thread 3 agrega las cosas y escribe staticVolatileTestString

Esto hace que sus líneas se pierdan. Volátil no ayuda aquí; todo el proceso de concatenación de la cuerda no es atómico.Es necesario utilizar un bloqueo en torno a esas operaciones:

private static object sync = new object(); 

lock (sync) { 
    staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
} 
+0

Sí, lo mismo que dije solo mejor explicado: D haha ​​+1 – Charleh

2

El MSDN describe lo que la palabra clave volatile hace here:

La palabra clave volátil indica que un campo puede ser modificado por varios subprocesos que se ejecutan simultáneamente. Los campos declarados volátiles no están sujetos a optimizaciones del compilador que asumen el acceso por un único hilo. Esto garantiza que el valor más actualizado sea presente en el campo en todo momento.

Esto significa, en su ejemplo, lo que sucede es sobre esto (esto puede variar de vez en cuando; depende del programador):

  • hilo 0 es la lectura de la cadena de staticVolatileTestString
  • hilo 0 está anexando 'hilo de ajuste 0 a 88 threadInt'
  • rosca 0 está escribiendo la cadena de nuevo en staticVolatileTestString

esto es como se esperaba hasta ahora, pero entonces:

  • hilo 1-4 están leyendo la cadena de staticVolatileTestString
  • hilo 1 se anexando 'hilo 1 ajuste threadInt a 97'
  • de rosca 2 está añadiendo 'rosca 2 threadInt entorno a 11'
  • rosca 2 está escribiendo la cadena de nuevo en staticVolatileTestString
  • ... hilos 1, 2, 3 están leyendo y anexar, y la escritura
  • hilo 4 es escribir la cadena de nuevo en staticVolatileTestString
  • y así sucesivamente ...

ver lo que pasó aquí? thread 4 leyó la cadena 'thread 0 setting threadInt to 88', agregó su 'thread 4 ...' y lo escribió de nuevo, sobrescribiendo todo lo que thread 1, 2 y 3 ya habían escrito en la cadena.

+0

¿Entonces está diciendo que los hilos están leyendo el valor simultáneamente y luego escribiendo como una operación separada uno sobre el otro? Si ese es el caso, necesitaría algún tipo de bloqueo en el campo (que es lo que pensé que era volátil, pero supongo que en realidad solo asegura que no haya almacenamiento en caché, ¿no?) EDIT: la respuesta de Lucero confirma esto – Xenoprimate

+0

@Motig: La cerradura está allí cuando lee la cuerda o la vuelve a escribir, pero no en el medio. Recuerde: las cadenas son inmutables, por lo tanto, se crea una nueva instancia de cadena, y la referencia a eso se escribe de nuevo en la variable. –

+2

@Motig: la palabra clave volátil funciona muy bien con los tipos de datos primitivos mutables (bool, int, double, etc.). Sin embargo, con strings, desea establecer un bloqueo dedicado, leer la cadena, modificarla y escribirla, luego liberar el bloqueo. –

Cuestiones relacionadas