Pregunta

Cuando la escritura de aplicaciones multiproceso, uno de los problemas más comunes experimentados son las condiciones de carrera.

Mis preguntas a la comunidad son:

¿Qué es una condición de carrera?¿Cómo detectarlos?¿Cómo se manejan?Finalmente, ¿cómo evitar que se produzcan?

¿Fue útil?

Solución

Una condición de carrera se produce cuando dos o más subprocesos pueden tener acceso a datos compartidos y tratan de cambiar al mismo tiempo.Porque el hilo del algoritmo de programación se pueden intercambiar entre los hilos en cualquier momento, usted no sabe el orden en el que los hilos intento de acceder a los datos compartidos.Por lo tanto, el resultado de la variación en los datos es dependiente en el hilo del algoritmo de programación, es decir,ambos hilos son "carreras" para acceder o cambiar los datos.

A menudo los problemas se producen cuando un hilo de hace un "check-entonces-actuar" (p. ej."comprobar" si el valor es X, entonces la "ley" a hacer algo que depende del valor de X) y otro hilo hace algo para el valor entre el "check" y la "ley".E. g:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

El punto es, y podría ser de 10, o podría ser cualquier cosa, dependiendo de si otro hilo cambiado x entre la verificación y la ley.Usted no tiene manera de saber.

Con el fin de evitar las condiciones de carrera que ocurra, lo normal sería poner un bloqueo en torno a los datos compartidos para asegurar que sólo un subproceso puede acceder a los datos en un momento.Esto significaría algo como esto:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x

Otros consejos

Una "race condition" existe cuando multiproceso (o de otro modo paralelo) código que facilitaría el acceso a un recurso compartido, puede hacerlo de tal manera como para producir resultados inesperados.

Tomemos este ejemplo:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

Si usted tenía 5 hilos de ejecutar este código a la vez, el valor de x NO terminaría siendo 50,000,000.Sería en realidad varían con cada ejecución.

Esto es debido a que, para cada subproceso para incrementar el valor de x, se tiene que hacer lo siguiente:(simplificado, obviamente)

Retrieve the value of x
Add 1 to this value
Store this value to x

Cualquier hilo puede ser en cualquier etapa de este proceso en cualquier momento, y que pueden intervenir en cada uno de los otros cuando un recurso está involucrado.El estado de x puede ser cambiado por otro hilo durante el tiempo entre la x que se está leyendo y cuando es volver a escribir.

Digamos que un hilo recupera el valor de x, pero no se ha guardado aún.Otro hilo también puede recuperar la mismo valor de x (porque no hay hilo ha cambiado todavía) y, a continuación, que sería el almacenamiento de la mismo el valor de (x+1) en x!

Ejemplo:

Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, valor es 7
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: tiendas de 8 x

Las condiciones de carrera puede ser evitado mediante el empleo de algún tipo de bloqueo mecanismo antes de que el código que tiene acceso al recurso compartido:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

Aquí, la respuesta sale como 50,000,000 cada vez.

Para más información sobre el bloqueo de la búsqueda para:exclusión mutua, de semáforo, de la sección crítica, el recurso compartido.

¿Qué es una Condición de Carrera?

Usted está planeando ir a ver una película a las 5 pm.Usted informarse acerca de la disponibilidad de las entradas a las 4 pm.El representante dice que están disponibles.Relajarse y llegar a la taquilla de 5 minutos antes de la exposición.Estoy seguro de que usted puede adivinar lo que sucede:es una casa completa.El problema aquí fue en la duración entre la verificación y la acción.Se preguntó a las 4 y actuó en 5.Mientras tanto, alguien la agarró de las entradas.Esa es una condición de carrera - específicamente un "check-entonces-de la" ley de situación de las condiciones de carrera.

¿Cómo detectarlos?

Religiosa de la revisión de código, multi-threaded de pruebas de unidad.No hay ningún acceso directo.Hay pocos plugin de Eclipse que emerge de este, pero nada estable todavía.

¿Cómo manejar y evitar que ellos?

Lo mejor sería crear libre de efectos secundarios y a los apátridas funciones, uso immutables tanto como sea posible.Pero eso no es siempre posible.Así que el uso de java.util.concurrente.atómica, de la concurrencia de las estructuras de datos, la sincronización correcta, y el actor basado en la simultaneidad será de ayuda.

El mejor recurso para la concurrencia es JCIP.También puede obtener algunos más detalles en la explicación anterior aquí.

Hay una importante diferencia técnica entre las condiciones de carrera y carreras de datos.La mayoría de las respuestas parecen hacer la suposición de que estos términos son equivalentes, pero no lo son.

Una condición de carrera se produce cuando 2 instrucciones de acceso a la misma ubicación de memoria, al menos uno de estos accesos es una escritura, y no hay sucede antes de ordenar entre estos accesos.Ahora, lo que constituye una sucede antes de hacer el pedido está sujeto a mucho debate, pero en general ulock-bloqueo de pares en el mismo bloqueo variable y esperar la señal de pares en la misma variable de condición de inducir un pasa-antes de orden.

Una condición de carrera es un error semántico.Es un defecto que se produce en el tiempo o en el orden de los eventos que lleva a la errónea programa comportamiento.

Muchas de las condiciones de carrera pueden ser (y de hecho son) causada por carreras de datos, pero esto no es necesario.Como cuestión de hecho, los datos de las razas y condiciones de carrera no son ni necesarios, ni la condición suficiente para que el uno al otro. Este blog que explica también la diferencia muy bien, con un simple banco de transacción ejemplo.Aquí es otro simple ejemplo que explica la diferencia.

Ahora que hemos clavado en la terminología, tratemos de responder a la pregunta original.

Dado que las condiciones de carrera son semánticos errores, no hay forma general para la detección de los mismos.Esto es debido a que no hay ninguna manera de tener un sistema automatizado de oracle que puede distinguir correcto vsincorrecto comportamiento del programa en el caso general.Carrera de detección es un problema indecidible.

Por otro lado, los datos de las carreras de tener una definición precisa que no se relaciona necesariamente con exactitud, y por lo tanto, uno puede detectar.Hay muchos sabores de datos de la carrera de los detectores (estático/dinámico de datos de la carrera de detección, juego de cerraduras de datos basados en la raza de detección, ocurre antes de la base de datos de la carrera de detección, híbrido de datos de la carrera de detección).Un estado del arte dinámico de datos de la carrera detector es ThreadSanitizer que funciona muy bien en la práctica.

Manejo de datos de las razas en general requiere un poco de programación de la disciplina para inducir ocurre antes de los bordes entre los accesos a los datos compartidos (ya sea durante el desarrollo, o que una vez detectado el uso de los instrumentos antes mencionados).esto se puede hacer a través de las cerraduras, las variables de condición, semáforos, etc.Sin embargo, también se pueden emplear diferentes paradigmas de programación, como el paso de mensajes (en lugar de la memoria compartida) que evite las carreras de datos por la construcción.

Una especie-de-canónica definición es "cuando dos hilos acceder a la misma ubicación en la memoria al mismo tiempo, y al menos uno de los accesos es una escritura." En la situación en la que el "lector" hilo se puede obtener el valor antiguo o el nuevo valor, dependiendo de que el hilo "gana la carrera." Esto no es siempre un error—de hecho, algunas muy peludas de bajo nivel de los algoritmos de hacerlo a propósito, pero en general se deben evitar.@Steve Gury dar un buen ejemplo de cuándo podría ser un problema.

Una condición de carrera es una especie de bug, que sólo ocurre con ciertas condiciones de temporal.

Ejemplo:Imagina que tienes dos hilos a y B.

En El Hilo:

if( object.a != 0 )
    object.avg = total / object.a

En El Subproceso B:

object.a = 0

Si Un hilo se anula sólo después de comprobar que el objeto.a es no nulo, B va a hacer a = 0, y al hilo de Un ganará el procesador, se va a hacer una "división por cero".

Este error sólo ocurre cuando Un hilo se adelantó justo después de la declaración de si, es muy raro, pero puede suceder.

Las condiciones de carrera ocurrir en aplicaciones multiproceso o multi-sistemas de proceso.Una condición de carrera, en su forma más básica, es algo que hace el supuesto de que dos cosas que no están en el mismo proceso o subproceso se suceden en un orden determinado, sin tomar las medidas para asegurarse de que lo hacen.Esto sucede comúnmente cuando dos hilos están de paso de mensajes mediante el ajuste y la comprobación de las variables miembro de una clase tanto puede tener acceso.Hay casi siempre una condición de carrera cuando un subproceso llama a dormir a dar otro hilo de tiempo para terminar una tarea (a menos de que el sueño es en un bucle, con algún mecanismo de comprobación).

Herramientas para la prevención de las condiciones de carrera son dependientes del lenguaje y el sistema operativo, pero algunos comon son los mutexes, las secciones críticas, y las señales.Los Mutexes son buenas cuando usted quiere asegurarse de que usted es el único que hace algo.Las señales son buenas cuando usted quiere asegurarse de que alguien ha terminado haciendo algo.La minimización de los recursos compartidos también puede ayudar a prevenir comportamientos inesperados

La detección de condiciones de carrera puede ser difícil, pero hay un par de signos.Código que depende en gran medida duerme es propenso a las condiciones de carrera, así que primero de verificación para las llamadas a dormir en el código afectado.Añadir particularmente largo duerme también puede ser utilizado para la depuración para intentar forzar un orden particular de eventos.Esto puede ser útil para reproducir el comportamiento, a ver si se puede hacer desaparecer por la modificación del calendario de las cosas, y para las pruebas de soluciones de poner en su lugar.El duerme debe ser eliminado después de la depuración.

La firma señal de que uno tiene una condición de carrera, aunque, es que si hay un tema que sólo se produce de forma intermitente en algunas máquinas.Los errores más comunes serían los accidentes y los interbloqueos.Con el registro, usted debería ser capaz de encontrar el área afectada y trabajar desde ahí.

Condición de carrera está relacionada no sólo con el software, pero también relacionado con el hardware.En realidad, el término fue inicialmente acuñado por la industria del hardware.

De acuerdo a wikipedia:

El término se origina con la idea de dos señales de carreras de cada uno de los otros a la influencia de la salida de la primera.

Condición de carrera en un circuito de lógica:

enter image description here

La industria del Software tomó este término sin ninguna modificación, lo que hace que sea un poco difícil de entender.

Usted necesita hacer una cierta sustitución de hacer un mapa para el mundo del software:

  • "dos señales" => "dos hilos"/"dos procesos"
  • "influir en el resultado" => "la influencia de algún estado compartido"

De modo que la condición de carrera en la industria del software significa "dos hilos"/"dos procesos de" carreras de cada uno de los otros para "influir en el estado compartido", y el resultado final del estado compartido dependerá de algunos sutil diferencia de tiempo, que podría ser causado por algún hilo específico/proceso de lanzamiento de pedido, subproceso o proceso de programación, etc.

Una condición de carrera es una situación en la programación concurrente, donde dos subprocesos simultáneos o procesos que compiten por un recurso y el resultado final del estado depende de que obtiene los recursos en primer lugar.

En realidad, Microsoft ha publicado una muy detallada artículo en esta materia de condiciones de carrera y los interbloqueos.La mayoría de resumen resumen de lo que sería el título del párrafo:

Una condición de carrera se produce cuando dos hilos acceder a una variable compartida en el mismo tiempo.El primer subproceso lee la variable, y la segunda hilo lee el mismo valor de la variable.A continuación, el primer hilo y segundo subproceso realizar sus operaciones en el valor, y que la carrera para ver el hilo que puede escribir el último valor de la variable compartida.El valor de la conversación en la que escribe su último valor se conserva, porque el hilo está escribiendo sobre el valor que el hilo anterior escribió.

¿Qué es una condición de carrera?

La situación cuando el proceso es críticamente dependiente de la secuencia o el calendario de eventos.

Por ejemplo, Procesador Un procesador y de la B ambas necesidades idéntica fuente de recursos para su ejecución.

¿Cómo detectarlos?

Existen herramientas para detectar la condición de carrera de forma automática:

¿Cómo se manejan?

Condición de carrera puede ser manejado por Objeto Mutex o Semáforos.Actúan como un bloqueo permite que un proceso para la adquisición de un recurso basado en una serie de requisitos para prevenir una condición de carrera.

¿Cómo se puede evitar que se produzcan?

Hay varias maneras de prevenir una condición de carrera, tales como La Sección Crítica De La Evitación De La.

  1. No hay dos procesos simultáneamente en el interior de sus regiones críticas.(Exclusión Mutua)
  2. No se realizan suposiciones acerca de la velocidad o el número de CPUs.
  3. No hay ningún proceso que se ejecuta fuera de su región crítica de los bloques que otros procesos.
  4. No hay ningún proceso tiene que esperar una eternidad para entrar en su región crítica.(Espera que para el B de los recursos, B espera para C recursos, C espera Un recursos)

Una condición de carrera es una situación indeseable que se produce cuando un dispositivo o sistema que intenta realizar dos o más operaciones al mismo tiempo, pero debido a la naturaleza del dispositivo o sistema, las operaciones deben realizarse en el orden correcto con el fin de ser hecho correctamente.

En la memoria del ordenador o de almacenamiento, una condición de carrera puede producirse si los comandos para leer y escribir una gran cantidad de datos que se reciben casi en el mismo instante, y la máquina intenta sobrescribir algunos o todos los datos antiguos, mientras que los datos todavía está siendo leído.El resultado puede ser uno o más de los siguientes:un desplome de la computadora, una "operación ilegal," notificación y cierre del programa, errores en la lectura de los datos o los errores de escritura de los nuevos datos.

Aquí está la clásica Saldo de la Cuenta Bancaria ejemplo que ayudará a los novatos a entender Hilos en Java fácilmente w.r.t.las condiciones de carrera:

public class BankAccount {

/**
 * @param args
 */
int accountNumber;
double accountBalance;

public synchronized boolean Deposit(double amount){
    double newAccountBalance=0;
    if(amount<=0){
        return false;
    }
    else {
        newAccountBalance = accountBalance+amount;
        accountBalance=newAccountBalance;
        return true;
    }

}
public synchronized boolean Withdraw(double amount){
    double newAccountBalance=0;
    if(amount>accountBalance){
        return false;
    }
    else{
        newAccountBalance = accountBalance-amount;
        accountBalance=newAccountBalance;
        return true;
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    BankAccount b = new BankAccount();
    b.accountBalance=2000;
    System.out.println(b.Withdraw(3000));

}

Pruebe este ejemplo básico para una mejor comprensión de la condición de carrera:

    public class ThreadRaceCondition {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Account myAccount = new Account(22222222);

        // Expected deposit: 250
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.DEPOSIT, 5.00);
            t.start();
        }

        // Expected withdrawal: 50
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.WITHDRAW, 1.00);
            t.start();

        }

        // Temporary sleep to ensure all threads are completed. Don't use in
        // realworld :-)
        Thread.sleep(1000);
        // Expected account balance is 200
        System.out.println("Final Account Balance: "
                + myAccount.getAccountBalance());

    }

}

class Transaction extends Thread {

    public static enum TransactionType {
        DEPOSIT(1), WITHDRAW(2);

        private int value;

        private TransactionType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    };

    private TransactionType transactionType;
    private Account account;
    private double amount;

    /*
     * If transactionType == 1, deposit else if transactionType == 2 withdraw
     */
    public Transaction(Account account, TransactionType transactionType,
            double amount) {
        this.transactionType = transactionType;
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        switch (this.transactionType) {
        case DEPOSIT:
            deposit();
            printBalance();
            break;
        case WITHDRAW:
            withdraw();
            printBalance();
            break;
        default:
            System.out.println("NOT A VALID TRANSACTION");
        }
        ;
    }

    public void deposit() {
        this.account.deposit(this.amount);
    }

    public void withdraw() {
        this.account.withdraw(amount);
    }

    public void printBalance() {
        System.out.println(Thread.currentThread().getName()
                + " : TransactionType: " + this.transactionType + ", Amount: "
                + this.amount);
        System.out.println("Account Balance: "
                + this.account.getAccountBalance());
    }
}

class Account {
    private int accountNumber;
    private double accountBalance;

    public int getAccountNumber() {
        return accountNumber;
    }

    public double getAccountBalance() {
        return accountBalance;
    }

    public Account(int accountNumber) {
        this.accountNumber = accountNumber;
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean deposit(double amount) {
        if (amount < 0) {
            return false;
        } else {
            accountBalance = accountBalance + amount;
            return true;
        }
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean withdraw(double amount) {
        if (amount > accountBalance) {
            return false;
        } else {
            accountBalance = accountBalance - amount;
            return true;
        }
    }
}

No siempre se desea descartar una condición de carrera.Si usted tiene un indicador que puede ser leído y escrito por varios hilos, y este indicador se establece en 'hecho' por un hilo para que otros tope de la rosca de procesamiento cuando el indicador se establece en 'hecho', usted no quiere que "la condición de carrera" para ser eliminado.De hecho, este puede ser referido como una benigna condición de carrera.

Sin embargo, el uso de una herramienta para la detección de la condición de carrera, va a ser vistos como un dañinos condición de carrera.

Más detalles sobre la condición de carrera aquí, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx.

Considere la posibilidad de una operación que tiene que mostrar el número tan pronto como el conteo se incrementa.es decir, tan pronto como CounterThread incrementa el valor DisplayThread debe mostrar el valor actualizado recientemente.

int i = 0;

Salida

CounterThread -> i = 1  
DisplayThread -> i = 1  
CounterThread -> i = 2  
CounterThread -> i = 3  
CounterThread -> i = 4  
DisplayThread -> i = 4

Aquí CounterThread recibe el bloqueo con frecuencia y se actualiza el valor antes de DisplayThread muestra de ello.Aquí existe una condición de Carrera.Condición de carrera puede ser resuelto mediante el uso de Synchronzation

Usted puede prevenir una condición de carrera, si el uso de "Atómica" de clases.La razón es sólo el hilo no separada de la operación get y set, el ejemplo es el siguiente:

AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);

Como resultado, usted tendrá 7 en el enlace "ai".Aunque se hicieron dos acciones, pero el tanto de la operación de confirmar el mismo hilo y no un hilo de otro va a interferir a esto, eso significa que no hay condiciones de carrera!

Una condición de carrera es una situación indeseable que se produce cuando dos o más procesos pueden acceder y modificar los datos compartidos al mismo tiempo.Ocurrió porque hubo conflicto accede a un recurso .La sección crítica de un problema que puede causar una condición de carrera.Para resolver la condición crítica entre el proceso de sacar sólo un proceso a la vez que se ejecutan de la sección crítica.

public class Synchronized_RACECONDITION {
    private static final int NUM_INCREMENTS = 10000;

    private static int count = 0;

    public static void main(String[] args) {
        testSyncIncrement();
        testNonSyncIncrement();
    }

    private static void testSyncIncrement() {
        count = 0;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, NUM_INCREMENTS)
                .forEach(i -> executor.submit(Synchronized_RACECONDITION::incrementSync));

        ConcurrentUtils.stop(executor);

        System.out.println("   Sync: " + count);
    }

    private static void testNonSyncIncrement() {
        count = 0;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, NUM_INCREMENTS)
                .forEach(i -> executor.submit(Synchronized_RACECONDITION::increment));

        ConcurrentUtils.stop(executor);

        System.out.println("NonSync: " + count);
    }

    private static synchronized void incrementSync() {
        count = count + 1;
    }

    private static void increment() {
        count = count + 1;
    }
static  class ConcurrentUtils {

    public static void stop(ExecutorService executor) {
        try {
            executor.shutdown();
            executor.awaitTermination(60, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
            System.err.println("termination interrupted");
        }
        finally {
            if (!executor.isTerminated()) {
                System.err.println("killing non-finished tasks");
            }
            executor.shutdownNow();
        }
    }
}
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top