2009-08-22 33 views
5

Estoy leyendo http://www.mono-project.com/ThreadsBeginnersGuide.C# Enhebrado: una condición de carrera ejemplo

El primer ejemplo tiene el siguiente aspecto:

public class FirstUnsyncThreads { 
    private int i = 0; 

    public static void Main (string[] args) { 
     FirstUnsyncThreads myThreads = new FirstUnsyncThreads(); 
    } 

    public FirstUnsyncThreads() { 
     // Creating our two threads. The ThreadStart delegate is points to 
     // the method being run in a new thread. 
     Thread firstRunner = new Thread (new ThreadStart (this.firstRun)); 
     Thread secondRunner = new Thread (new ThreadStart (this.secondRun)); 

     // Starting our two threads. Thread.Sleep(10) gives the first Thread 
     // 10 miliseconds more time. 
     firstRunner.Start(); 
     Thread.Sleep (10); 
     secondRunner.Start(); 
    } 

    // This method is being excecuted on the first thread. 
    public void firstRun() { 
     while(this.i < 10) { 
      Console.WriteLine ("First runner incrementing i from " + this.i + 
           " to " + ++this.i); 
      // This avoids that the first runner does all the work before 
      // the second one has even started. (Happens on high performance 
      // machines sometimes.) 
      Thread.Sleep (100); 
     } 
    } 

    // This method is being excecuted on the second thread. 
    public void secondRun() { 
     while(this.i < 10) { 
      Console.WriteLine ("Second runner incrementing i from " + this.i + 
           " to " + ++this.i); 
      Thread.Sleep (100); 
     } 
    } 
} 

Salida:

First runner incrementing i from 0 to 1 
Second runner incrementing i from 1 to 2 
Second runner incrementing i from 3 to 4 
First runner incrementing i from 2 to 3 
Second runner incrementing i from 5 to 6 
First runner incrementing i from 4 to 5 
First runner incrementing i from 6 to 7 
Second runner incrementing i from 7 to 8 
Second runner incrementing i from 9 to 10 
First runner incrementing i from 8 to 9 

Wow, ¿qué es esto? Desafortunadamente, la explicación en el artículo es inadecuada para mí. ¿Puede explicarme por qué los incrementos ocurrieron en una orden desordenada?

Gracias!

+0

El autor del artículo mencionado dice que la salida "puede" ser como se muestra. Esto es cierto en el sentido de que no se puede probar que la salida no sea como se muestra. Sin embargo, en la práctica dado que a First se le da aproximadamente una ventaja inicial de 10 ms, esperaría ver alternancia de paso de bloqueo durante bastantes iteraciones antes de que ocurra cualquier "confuso". –

Respuesta

2

La sincronización es esencial cuando hay varios subprocesos. En este caso, verá que ambos hilos leen y escriben en this.i, pero no se realiza ningún buen intento para sincronizar estos accesos. Como ambos modifican al mismo tiempo la misma área de memoria, observa la salida mezclada. La llamada a Sleep es peligrosa, es un enfoque que conduce a errores seguros. No puede suponer que los hilos siempre serán desplazados por los 10 ms iniciales.

En resumen: nunca use el modo de suspensión para la sincronización :-) sino que adopte algún tipo de técnica de sincronización de hilos (por ejemplo, bloqueos, mutexes, semáforos). Siempre intente utilizar el candado más ligero posible que satisfaga sus necesidades ....

Un recurso útil es el libro de Joe Duffy, Programación concurrente en Windows.

+0

No creo que Thread.Sleep() se use para intentar y sincronizar los subprocesos. Se acaba de usar para que los incrementos sean observables (de lo contrario, todo aparecería a la vez en la consola), lo que tiene un efecto secundario de reducir la posibilidad de una condición de carrera a casi cero. –

+0

Sí, el comentario dice que, aunque en realidad se usa como una técnica de sincronización aproximada, observe la brecha de 10 ms entre las dos llamadas a Inicio(). En ausencia de otras técnicas, me parece que Sleep está simulando una sincronización entre los dos hilos. – Francesco

+0

+1 para el libro de Joe Duffy, es la Biblia Concurrencia de Windows –

0

Los incrementos no están sucediendo fuera de servicio, la Console.WriteLine (...) está escribiendo la salida de múltiples hilos en una consola de un solo subproceso, y la sincronización de muchos hilos a un hilo está causando los mensajes a aparecen fuera de servicio.

Supongo que este ejemplo intentó crear una condición de carrera, y en su caso falló. Desafortunadamente, los problemas de concurrencia, como una condición de carrera y puntos muertos, son difíciles de predecir y reproducir debido a su naturaleza. Es posible que desee intentar ejecutarlo unas cuantas veces más, modificarlo para usar más hilos y cada hilo debería incrementarse más (digamos 100,000). Entonces puede ver que el resultado final no será igual a la suma de todos los incrementos (causado por una condición de carrera).

+0

Debug.WriteLine? –

+0

Lo sentimos, Console.WriteLine. Corregido –

2

Cuando ejecuto esto (en un doble núcleo), mi salida es

First runner incrementing i from 0 to 1 
Second runner incrementing i from 1 to 2 
First runner incrementing i from 2 to 3 
Second runner incrementing i from 3 to 4 
First runner incrementing i from 4 to 5 
Second runner incrementing i from 5 to 6 
First runner incrementing i from 6 to 7 
Second runner incrementing i from 7 to 8 
First runner incrementing i from 8 to 9 
Second runner incrementing i from 9 to 10 

Como yo hubiera esperado. Está ejecutando dos bucles, ambos ejecutan Sleep (100). Eso es muy inadecuado para demostrar una condición de carrera.

El código tiene una condición de carrera (como describe VoteyDisciple) pero es muy poco probable que salga a la superficie.

No puedo explicar la falta de orden en su salida (¿es una salida real?), Pero la clase de la consola sincronizará las llamadas de salida.

Si omite las llamadas de Suspensión() y ejecuta los bucles 1000 veces (en lugar de 10), puede ver dos corredores que se incrementan de 554 a 555 o algo así.

+0

Acabo de copiar lo que tenía el artículo, sin embargo, pude lograr una salida desordenada similar al eliminar la suspensión inicial en el programa y bajar la capacidad en los hilos a 20. – George

+0

Tiene razón, la causa está dentro de la sincronización de la consola. –

3

Creo que el escritor del artículo ha confundido las cosas.

VoteyDisciple es correcto que ++i no es atómico y puede producirse una condición de carrera si el objetivo no se bloquea durante la operación, pero esto no causará el problema descrito anteriormente.

Si una condición de anticipación se produce llamando ++i operaciones a continuación internos del titular ++ quedará del siguiente modo: -

  1. primera hilo lee el valor 0
  2. segundo hilo lee el valor 0
  3. valor incrementos primera rosca a 1
  4. El segundo hilo incrementa el valor a 1
  5. El primer hilo escribe el valor 1
  6. segundo hilo escribe el valor 1

El orden de las operaciones de 3 a 6 no es importante, el punto es que tanto las operaciones de lectura, 1 y 2, se pueden producir cuando la variable tiene un valor x que resulta en la misma incrementación a y, en lugar de cada subproceso, realizando incrementos para valores distintos de xey.

Esto puede resultar en la siguiente salida: -

First runner incrementing i from 0 to 1 
Second runner incrementing i from 0 to 1 

Lo que sería aún peor es la siguiente: -

  1. primera hilo lee el valor 0
  2. segundo hilo lee el valor 0
  3. El segundo hilo aumenta el valor a 1
  4. El segundo hilo escribe el valor 1
  5. segundo hilo lee el valor 1
  6. segundo valor incrementos de rosca a 2
  7. segundo hilo escribe el valor 2
  8. primero valor incrementos de rosca a 1
  9. primera rosca escribe el valor 1
  10. segundo hilo lee el valor 1
  11. valor
  12. incrementos de 2º de rosca a 2
  13. segundo hilo escribe el valor 2

Esto puede dar lugar a la siguiente salida: -

First runner incrementing i from 0 to 1 
Second runner incrementing i from 0 to 1 
Second runner incrementing i from 1 to 2 
Second runner incrementing i from 1 to 2 

Y así sucesivamente.

Por otra parte, existe una posible condición de carrera entre la lectura y la realización de i++i ya que las llamadas concatena Console.WriteLine i y ++i. Esto puede resultar en una salida como: -

First runner incrementing i from 0 to 1 
Second runner incrementing i from 1 to 3 
First runner incrementing i from 1 to 2 

La salida de la consola desordenadas que el escritor ha descrito sólo puede ser el resultado de la imprevisibilidad de la salida de la consola y no tiene nada que ver con una condición de carrera en la variable i. Tener un bloqueo en i mientras realiza ++i o mientras concatena i y ++i no cambiará este comportamiento.

Cuestiones relacionadas