Condiciones de carrera y interbloqueos

Visual Basic .NET o Visual Basic ofrece la capacidad de usar subprocesos en aplicaciones de Visual Basic por primera vez. Los subprocesos presentan problemas de depuración, como condiciones de carrera y interbloqueos. En este artículo se exploran estos dos problemas.

Versión original del producto: Visual Basic, Visual Basic .NET
Número de KB original: 317723

Cuando se produzcan condiciones de carrera

Una condición de carrera se produce cuando dos subprocesos acceden a una variable compartida al mismo tiempo. El primer subproceso lee la variable y el segundo subproceso lee el mismo valor de la variable. A continuación, el primer subproceso y el segundo subproceso realizan sus operaciones en el valor y compiten para ver qué subproceso puede escribir el último valor en la variable compartida. Se conserva el valor del subproceso que escribe su último valor, porque el subproceso escribe sobre el valor que escribió el subproceso anterior.

Detalles y ejemplos de una condición de carrera

A cada subproceso se le asigna un período de tiempo predefinido para ejecutarse en un procesador. Cuando expira el tiempo asignado para el subproceso, el contexto del subproceso se guarda hasta que se activa el procesador y el procesador inicia la ejecución del subproceso siguiente.

¿Cómo puede un comando de una línea provocar una condición de carrera?

Examine el ejemplo siguiente para ver cómo se produce una condición de carrera. Hay dos subprocesos y ambos actualizan una variable compartida denominada total (que se representa como dword ptr ds:[031B49DCh] en el código del ensamblado).

  • Subproceso 1

    Total = Total + val1
    
  • Subproceso 2

    Total = Total - val2
    

Código de ensamblado (con números de línea) de la compilación del código anterior de Visual Basic:

  • Subproceso 1

    1. mov eax,dword ptr ds:[031B49DCh]
    2. add eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 7611097F
    6. mov dword ptr ds:[031B49DCh],eax
    
  • Subproceso 2

    1. mov eax,dword ptr ds:[031B49DCh]
    2. sub eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 76110BE7
    6. mov dword ptr ds:[031B49DCh],eax
    

Al examinar el código del ensamblado, puede ver cuántas operaciones está realizando el procesador en el nivel inferior para ejecutar un cálculo de suma simple. Un subproceso puede ejecutar todo o parte de su código de ensamblado durante su tiempo en el procesador. Ahora examine cómo se produce una condición de carrera a partir de este código.

Total es 100, val1 es 50 y val2 es 15. El subproceso 1 tiene la oportunidad de ejecutarse, pero solo completa los pasos del 1 al 3. Esto significa que el subproceso 1 leyó la variable y completó la adición. El subproceso 1 está ahora a la espera de escribir su nuevo valor de 150. Una vez detenido el subproceso 1 , el subproceso 2 se ejecuta por completo. Esto significa que ha escrito el valor que calculó (85) en la variable Total. Por último, el subproceso 1 recupera el control y finaliza la ejecución. Escribe su valor (150). Por lo tanto, cuando finaliza el subproceso 1 , el valor de Total es ahora 150 en lugar de 85.

Puede ver cómo podría ser un problema importante. Si se trata de un programa bancario, el cliente tendría dinero en su cuenta que no debería estar presente.

Este error es aleatorio, ya que es posible que el subproceso 1 complete su ejecución antes de que expire el tiempo en el procesador y, a continuación, el subproceso 2 puede comenzar su ejecución. Si se producen estos eventos, el problema no se produce. La ejecución de subprocesos no es determinista, por lo que no se puede controlar el tiempo ni el orden de ejecución. Tenga en cuenta también que los subprocesos pueden ejecutarse de forma diferente en tiempo de ejecución frente al modo de depuración. Además, puede ver que si ejecuta cada subproceso de la serie, el error no se produce. Esta aleatoriedad hace que estos errores sea mucho más difícil de rastrear y depurar.

Para evitar que se produzcan las condiciones de carrera, puede bloquear las variables compartidas, de modo que solo un subproceso tenga acceso a la variable compartida. Haga esto con moderación, porque si una variable está bloqueada en thread 1 y thread 2 también necesita la variable, la ejecución del subproceso 2 se detiene mientras thread 2 espera a que thread 1 libere la variable. (Para obtener más información, consulte SyncLock la sección Referencias de este artículo).

Síntomas de una condición de raza

El síntoma más común de una condición de carrera son los valores impredecibles de las variables que se comparten entre varios subprocesos. Esto se debe a la imprevisibilidad del orden en que se ejecutan los subprocesos. En algún momento gana un subproceso y en algún momento gana el otro. En otras ocasiones, la ejecución funciona correctamente. Además, si cada subproceso se ejecuta por separado, el valor de la variable se comporta correctamente.

Cuando se producen interbloqueos

Un interbloqueo se produce cuando dos subprocesos bloquean una variable diferente al mismo tiempo y, a continuación, intentan bloquear la variable que el otro subproceso ya ha bloqueado. Como resultado, cada subproceso deja de ejecutarse y espera a que el otro subproceso libere la variable. Dado que cada subproceso contiene la variable que el otro subproceso quiere, no se produce nada y los subprocesos permanecen interbloqueados.

Detalles y ejemplos de interbloqueos

El código siguiente tiene dos objetos y LeftValRightVal:

  • Subproceso 1

    SyncLock LeftVal
        SyncLock RightVal
            'Perform operations on LeftVal and RightVal that require read and write.
        End SyncLock
    End SyncLock
    
  • Subproceso 2

    SyncLock RightVal
        SyncLock LeftVal
            'Perform operations on RightVal and LeftVal that require read and write.
        End SyncLock
    End SyncLock
    

Se produce un interbloqueo cuando se permite al subproceso 1 bloquear LeftVal. El procesador detiene la ejecución del subproceso 1 y comienza la ejecución del subproceso 2. El subproceso 2 se bloquea RightVal y, a continuación, intenta bloquear LeftVal. Dado que LeftVal está bloqueado, el subproceso 2 se detiene y espera LeftVal a que se libere. Dado que el subproceso 2 está detenido, el subproceso 1 puede continuar ejecutándose. El subproceso 1 intenta bloquearse RightVal , pero no puede, porque el subproceso 2 lo ha bloqueado. Como resultado, el subproceso 1 comienza a esperar hasta que RightVal esté disponible. Cada subproceso espera al otro subproceso, ya que cada subproceso ha bloqueado la variable en la que está esperando el otro subproceso y ninguno de los subprocesos desbloquea la variable que contiene.

No siempre se produce un interbloqueo. Si Thread 1 ejecuta ambos bloqueos antes de que el procesador lo detenga, Thread 1 puede realizar sus operaciones y, a continuación, desbloquear la variable compartida. Después de que Thread 1 desbloquee la variable, Thread 2 puede continuar con su ejecución, según lo previsto.

Este error parece obvio cuando estos fragmentos de código se colocan en paralelo, pero, en la práctica, el código puede aparecer en módulos o áreas independientes del código. Se trata de un error difícil de rastrear porque, a partir de este mismo código, puede producirse una ejecución correcta y una ejecución incorrecta.

Síntomas de interbloqueos

Un síntoma común de interbloqueo es que el programa o grupo de subprocesos deja de responder. Esto también se conoce como bloqueo. Al menos dos subprocesos están esperando una variable que el otro subproceso bloqueó. Los subprocesos no continúan, ya que ninguno de los subprocesos liberará su variable hasta que obtenga la otra variable. Todo el programa puede bloquearse si el programa está esperando en uno o ambos subprocesos para completar la ejecución.

¿Qué es un subproceso?

Los procesos se usan para separar las distintas aplicaciones que se ejecutan en un momento determinado en un único equipo. El sistema operativo no ejecuta procesos, pero sí subprocesos. Un subproceso es una unidad de ejecución. El sistema operativo asigna el tiempo de procesador a un subproceso para la ejecución de las tareas del subproceso. Un único proceso puede contener varios subprocesos de ejecución. Cada subproceso mantiene sus propios controladores de excepciones, las prioridades de programación y un conjunto de estructuras que el sistema operativo usa para guardar el contexto del subproceso si el subproceso no puede completar su ejecución durante el tiempo que se asignó al procesador. El contexto se mantiene hasta la próxima vez que el subproceso recibe el tiempo de procesador. El contexto incluye toda la información que requiere el subproceso para continuar sin problemas su ejecución. Esta información incluye el conjunto de registros de procesador del subproceso y la pila de llamadas dentro del espacio de direcciones del proceso de host.

Referencias

Para obtener más información, busque las siguientes palabras clave en la Ayuda de Visual Studio:

  • SyncLock. Permite bloquear un objeto. Si otro subproceso intenta bloquear ese mismo objeto, se bloquea hasta que se libera el primer subproceso. Use SyncLock con cuidado, ya que los problemas pueden deberse al uso incorrecto de SyncLock. Por ejemplo, este comando puede evitar condiciones de carrera, pero provocar interbloqueos.

  • InterLocked. Permite un conjunto selecto de operaciones seguras para subprocesos en variables numéricas básicas.

Para obtener más información, vea Subprocesos y subprocesos.