I understand that the race condition occurs when two threads access the same state, each one modifying it on its own. After this, the resulting value will be different than expected and may even be different on each run. Here an example of a critical section (in Java):
public class Counter {
protected long count = 0;
public void add(long value){
this.count = this.count + value;
}
}
If Pedro and Ana execute the add() function in a different thread, one adds 5 and the other adds 2. The result will be the value written by the last thread. Therefore, if Pedro's thread is executed last, the value will be 5. But if Ana's thread is executed last, the value will be 2. What really should have happened is that the two values were added and the final result out 7.
In Scala applying "functional programming" they tell us to avoid shared states and that we must use immutable structures such as ADTs. But to understand this solution I need an example where this concurrence is simulated, it can be that of a consignment in a savings account. Let's say that Luis and Maria deposit $10 and $5 respectively in the same account at the same time. Suppose that the account information is stored in a database, we are also working with futures in Scala and in addition to that, when making a deposit, the system immediately gives you the current balance. How does immutability and unshared states play into the user being shown the correct information under this scenario?
Here is the scala code that exemplifies the race condition:
import scala.concurrent.Future
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
trait Cuenta
case class CuentaAhorros(numero:String, cantidad:BigDecimal, idUser:String) extends Cuenta
trait OperacionesCuenta {
def buscarCuenta(numeroCuenta:String):Future[CuentaAhorros] = Future{
// supongamos que fuimos a bd y nos trajo la cuenta
CuentaAhorros("021-123-456-11", 20, "1067905803")
}
def consignar(cuentaAhorros: CuentaAhorros, valor:BigDecimal):Future[Cuenta] = Future{
cuentaAhorros.copy(cantidad = cuentaAhorros.cantidad + valor)
}
def transaccion(numeroCuenta:String, valor:BigDecimal): Future[Cuenta] =
for {
cuentaObjetivo <- buscarCuenta(numeroCuenta)
cuentaActualizada <- consignar(cuentaObjetivo, valor)
}yield cuentaActualizada
}
object Operaciones extends App with OperacionesCuenta{
val transaccion1: Future[Cuenta] = transaccion("021-123-456-11", 20)
val transaccion2: Future[Cuenta] = transaccion("021-123-456-11", 40)
transaccion1 onComplete {
case Success(cuenta) => println("Cuenta en la trasacción 1 = " + cuenta)
case Failure(e) => println("A ocurrido un error: " + e.getMessage)
}
transaccion2 onComplete {
case Success(cuenta) => println("Cuenta en la trasacción 2 = " + cuenta)
case Failure(e) => println("A ocurrido un error: " + e.getMessage)
}
Thread.sleep(2000)
}
The result of the execution was the following:
Account in transaction 1: CuentaAhorros(021-123-456-11,40,1067905803)
Account in transaction 2: CuentaAhorros(021-123-456-11,60,1067905803)