multithreading - threading - thread python 3
¿Qué es una condición de raza? (17)
Al escribir aplicaciones de subprocesos múltiples, uno de los problemas más comunes que se experimentan son las condiciones de carrera.
Mis preguntas a la comunidad son:
¿Qué es una condición de raza? ¿Cómo los detectas? ¿Cómo los manejas? Finalmente, ¿cómo evitas que ocurran?
¿Qué es una condición de raza?
Usted está planeando ir a ver una película a las 5 pm. Consulta sobre la disponibilidad de las entradas a las 16 hs. El representante dice que están disponibles. Te relajas y llegas a la taquilla 5 minutos antes del show. Estoy seguro de que puedes adivinar lo que pasa: es una casa llena. El problema aquí estaba en la duración entre la verificación y la acción. Preguntó a las 4 y actuó a las 5. Mientras tanto, alguien más tomó las entradas. Esa es una condición de carrera, específicamente un escenario de "verificación y acto" de las condiciones de la carrera.
¿Cómo los detectas?
Revisión de código religioso, pruebas unitarias multi-hilo. No hay ningún atajo. Hay pocos complementos de Eclipse que surgen en esto, pero todavía no hay nada estable.
¿Cómo los manejas y evitas?
Lo mejor sería crear funciones sin efectos secundarios y sin estado, usar lo más posible los inmutables. Pero eso no siempre es posible. Así que el uso de java.util.concurrent.atomic, las estructuras de datos concurrentes, la sincronización adecuada y la concurrencia basada en actores ayudará.
El mejor recurso para la concurrencia es JCIP. También puede obtener más detalles sobre la explicación anterior aquí .
Aquí está el ejemplo clásico de saldo de cuenta bancaria que ayudará a los principiantes a comprender los temas en Java fácilmente en 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));
}
Considere una operación que tenga que mostrar el conteo tan pronto como el conteo se incremente. es decir, tan pronto como CounterThread incremente 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 obtiene el bloqueo con frecuencia y actualiza el valor antes de que DisplayThread lo muestre. Aquí existe una condición de raza. Condición de carrera se puede resolver mediante el uso de sincronización
En realidad, Microsoft ha publicado un article muy detallado sobre este tema de condiciones de carrera y puntos muertos. El resumen más resumido sería el párrafo del título:
Se produce una condición de carrera cuando dos subprocesos acceden a una variable compartida al mismo tiempo. El primer hilo lee la variable, y el segundo hilo lee el mismo valor de la variable. Luego, 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. El valor del hilo que escribe su último valor se conserva, porque el hilo está escribiendo sobre el valor que escribió el hilo anterior.
Existe una "condición de carrera" cuando el código de subprocesos múltiples (o de otro modo paralelo) que accedería a un recurso compartido podría hacerlo de tal manera que cause resultados inesperados.
Tomemos este ejemplo:
for ( int i = 0; i < 10000000; i++ )
{
x = x + 1;
}
Si tuviera 5 hilos ejecutando este código a la vez, el valor de x NO terminaría siendo 50,000,000. De hecho, variaría con cada carrera.
Esto se debe a que, para que cada hilo incremente el valor de x, deben hacer lo siguiente: (simplificado, obviamente)
Retrieve the value of x Add 1 to this value Store this value to x
Cualquier subproceso puede estar en cualquier paso de este proceso en cualquier momento, y pueden pisarse entre ellos cuando se trata de un recurso compartido. El estado de x se puede cambiar por otro hilo durante el tiempo entre x se está leyendo y cuando se vuelve a escribir.
Digamos que un hilo recupera el valor de x, pero aún no lo ha almacenado. ¡Otro hilo también puede recuperar el mismo valor de x (porque ningún hilo lo ha cambiado todavía) y luego ambos estarían almacenando el mismo valor (x + 1) de nuevo en x!
Ejemplo:
Thread 1: reads x, value is 7 Thread 1: add 1 to x, value is now 8 Thread 2: reads x, value is 7 Thread 1: stores 8 in x Thread 2: adds 1 to x, value is now 8 Thread 2: stores 8 in x
Las condiciones de carrera pueden evitarse empleando algún tipo de mecanismo de bloqueo antes del código que accede 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, busque: mutex, semáforo, sección crítica, recurso compartido.
Hay una diferencia técnica importante entre las condiciones de carrera y las carreras de datos. La mayoría de las respuestas parecen suponer que estos términos son equivalentes, pero no lo son.
Una carrera de datos se produce cuando 2 instrucciones acceden a la misma ubicación de memoria, al menos uno de estos accesos es una escritura y no ocurre antes de ordenar entre estos accesos. Ahora, lo que constituye un suceso antes de ordenar está sujeto a un gran debate, pero en general los pares de ulock-lock en la misma variable de bloqueo y los pares de señal de espera en la misma variable de condición inducen un orden de suceso antes.
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 conduce a un comportamiento erróneo del programa.
Muchas condiciones de carrera pueden ser (y de hecho son) causadas por carreras de datos, pero esto no es necesario. De hecho, las razas de datos y las condiciones de las carreras no son una condición necesaria, ni una condición suficiente entre sí. This publicación del blog también explica muy bien la diferencia, con un simple ejemplo de transacción bancaria. Aquí hay otro example simple que explica la diferencia.
Ahora que hemos definido la terminología, intentemos responder la pregunta original.
Dado que las condiciones de la raza son errores semánticos, no hay una forma general de detectarlos. Esto se debe a que no hay forma de tener un oráculo automatizado que pueda distinguir el comportamiento correcto del programa en el caso general. La detección de la raza es un problema indecidible.
Por otro lado, las carreras de datos tienen una definición precisa que no se relaciona necesariamente con la corrección y, por lo tanto, se pueden detectar. Hay muchos tipos de detectores de carrera de datos (detección de carrera de datos estática / dinámica, detección de carrera de datos basada en bloqueo, detección de carrera de datos basada en eventos anteriores, detección de carrera de datos híbrida). Un detector de carreras de datos dinámicos de última generación es ThreadSanitizer que funciona muy bien en la práctica.
En general, el manejo de las carreras de datos requiere cierta disciplina de programación para inducir la aparición de bordes entre accesos a datos compartidos (ya sea durante el desarrollo o una vez que se detectan utilizando las herramientas mencionadas anteriormente). esto se puede hacer a través de bloqueos, 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 evitan las carreras de datos por construcción.
La condición de carrera no solo está relacionada con el software, sino también con el hardware. En realidad, el término fue acuñado inicialmente por la industria del hardware.
Según wikipedia :
El término se origina con la idea de dos señales que compiten entre sí para influir primero en la salida .
Condición de carrera en un circuito lógico:
La industria del software tomó este término sin modificaciones, lo que lo hace un poco difícil de entender.
Necesita hacer algún reemplazo para asignarlo al mundo del software:
- "dos señales" => "dos hilos" / "dos procesos"
- "influenciar la salida" => "influenciar algún estado compartido"
Por lo tanto, la condición de carrera en la industria del software significa "dos hilos" / "dos procesos" compitiendo entre sí para "influir en algún estado compartido", y el resultado final del estado compartido dependerá de alguna diferencia de tiempo sutil, que podría ser causada por algunos factores específicos. orden de inicio de subproceso / proceso, programación de subproceso / proceso, etc.
Las condiciones de carrera ocurren en aplicaciones de subprocesos múltiples o sistemas de procesos múltiples. Una condición de carrera, en su forma más básica, es cualquier cosa que haga la suposición de que dos cosas que no están en el mismo hilo o proceso sucederán en un orden particular, sin tomar medidas para asegurar que lo hagan. Esto ocurre comúnmente cuando dos hilos pasan mensajes al configurar y verificar las variables de miembros de una clase a las que ambos pueden acceder. Casi siempre hay una condición de carrera cuando un subproceso llama a dormir para que otro subproceso tenga tiempo para finalizar una tarea (a menos que el sueño esté en un bucle, con algún mecanismo de verificación)
Las herramientas para prevenir las condiciones de carrera dependen del lenguaje y del sistema operativo, pero algunas de ellas son mutex, secciones críticas y señales. Los Mutex son buenos cuando quieres asegurarte de que eres el único que está haciendo algo. Las señales son buenas cuando quiere asegurarse de que alguien más haya terminado de hacer algo. Minimizar los recursos compartidos también puede ayudar a prevenir comportamientos inesperados
Detectar las condiciones de la carrera puede ser difícil, pero hay un par de señales. El código que depende en gran medida de los tiempos de espera es propenso a las condiciones de carrera, por lo que primero debe verificar si hay llamadas para dormir en el código afectado. La adición de tiempos de espera particularmente largos también se puede usar para la depuración para intentar forzar un orden particular de eventos. Esto puede ser útil para reproducir el comportamiento, ver si puede hacer que desaparezca cambiando el tiempo de las cosas y para probar las soluciones implementadas. Las suspensiones deben eliminarse después de la depuración.
Sin embargo, el signo distintivo de que uno tiene una condición de carrera es si hay un problema que solo ocurre de manera intermitente en algunas máquinas. Errores comunes serían choques y puntos muertos. Con el registro, debería poder encontrar el área afectada y volver a trabajar desde allí.
No siempre quieres descartar una condición de carrera. Si tiene un indicador que puede leerse y escribirse en varios subprocesos, y este indicador está establecido en ''hecho'' por un hilo para que el otro proceso de detención del hilo cuando el indicador esté establecido en ''hecho'', no desee que "carrera" condición "para ser eliminado. De hecho, esta puede ser referida como una condición de carrera benigna.
Sin embargo, utilizando una herramienta para la detección de la condición de carrera, se verá como una condición de carrera dañina.
Más detalles sobre la condición de la carrera aquí, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx .
Ok, eso es 4 preguntas. Una por una respuesta es como debajo de ...
¿Qué es una condición de raza?
Ocurre cuando la salida y / o el resultado del proceso dependen críticamente de la secuencia o el tiempo de otros eventos, es decir, las señales de 2g están compitiendo para cambiar la salida primero.
¿Cómo los detectas?
Conduce al error que es difícil de localizar.
¿Cómo los manejas?
Utilizar semáforos
Y finalmente,
¿Cómo evitas que ocurran?
Una forma de evitar la condición de carrera es usar el mecanismo de bloqueo de los recursos. pero los recursos de bloqueo pueden llevar a puntos muertos. que tiene que ser tratado.
Pruebe este ejemplo básico para comprender mejor la condición de la raza:
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;
}
}
}
Puedes prevenir la condición de raza , si usas clases "Atómicas". El motivo es que el subproceso no separa y configura la operación, el siguiente ejemplo:
AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);
Como resultado, tendrá 7 en el enlace "ai". Aunque hiciste dos acciones, pero ambas operaciones confirman el mismo hilo y ningún otro hilo interferirá en esto, ¡eso significa que no hay condiciones de carrera!
Se produce una condición de carrera cuando dos o más subprocesos pueden acceder a datos compartidos e intentan cambiarlos al mismo tiempo. Debido a que el algoritmo de programación de subprocesos puede intercambiar entre subprocesos en cualquier momento, no sabe el orden en el que los subprocesos intentarán acceder a los datos compartidos. Por lo tanto, el resultado del cambio en los datos depende del algoritmo de programación de subprocesos, es decir, ambos subprocesos están "compitiendo" para acceder / cambiar los datos.
Los problemas a menudo ocurren cuando un hilo hace un "check-then-act" (por ejemplo, "check" si el valor es X, luego "actúa" para hacer algo que depende del valor de X) y otro thread hace algo al valor en entre el "cheque" y el "acto". P.ej:
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 que y podría ser 10, o podría ser cualquier cosa, dependiendo de si otro hilo cambió x entre el cheque y el acto. No tienes una forma real de saber.
Con el fin de evitar que ocurran condiciones de carrera, por lo general, se pone un candado alrededor de los datos compartidos para garantizar que solo un hilo pueda acceder a los datos a la vez. 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
Una condición de carrera es un tipo de error que solo ocurre en ciertas condiciones temporales.
Ejemplo: Imagina que tienes dos hilos, A y B.
En el hilo A:
if( object.a != 0 )
object.avg = total / object.a
En el hilo B:
object.a = 0
Si el hilo A se sustituye justo después de verificar que object.a no es nulo, B hará a = 0
, y cuando el subproceso A ganará el procesador, hará una "división por cero".
Este error solo ocurre cuando el hilo A se anula justo después de la sentencia if, es muy raro, pero puede suceder.
Una condición de carrera es una situación en la programación concurrente donde dos subprocesos o procesos concurrentes y el estado final resultante dependen de quién obtiene el recurso primero.
Una condición de carrera es una situación indeseable que ocurre cuando un dispositivo o sistema intenta realizar dos o más operaciones al mismo tiempo, pero debido a la naturaleza del dispositivo o sistema, las operaciones deben realizarse en la secuencia correcta para que hecho correctamente
En la memoria de la computadora o en el almacenamiento, se puede producir una condición de carrera si se reciben comandos para leer y escribir una gran cantidad de datos casi en el mismo instante, y la máquina intenta sobrescribir algunos o todos los datos antiguos mientras esos datos antiguos aún están en uso. leer. El resultado puede ser uno o más de los siguientes: un bloqueo del equipo, una "operación ilegal", notificación y apagado del programa, errores al leer los datos antiguos o errores al escribir los nuevos datos.
Una definición de tipo canónico es " cuando dos subprocesos acceden 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, el subproceso "lector" puede obtener el valor antiguo o el nuevo valor, dependiendo de qué subproceso "gana la carrera". Esto no siempre es un error; de hecho, algunos algoritmos de bajo nivel realmente peludos lo hacen a propósito, pero generalmente se debe evitar. @Steve Gury da un buen ejemplo de cuándo podría ser un problema.