Computación concurrente

paradigma de programación

La computación concurrente es una forma de cómputo en la cual varios cálculos se realizan concurrentemente, y no uno a la vez de forma secuencial.

Es una característica propia de un sistema, ya sea un programa, una computadora o una red, en la cual existe un punto separado de ejecución o "hilo de control" para cada proceso. Un sistema concurrente es aquel donde un cálculo puede avanzar sin esperar a que el resto de los cálculos se completen.[1]

La computación concurrente se considera una forma de programación modular. Dentro de este paradigma un cálculo entero es factorado en varios sub cálculos que podrían ejecutarse concurrentemente. Los pioneros en este campo incluyen a Edsger Dijkstra, Per Brinch Hansen, y C. A. R. Hoare.

Introducción

editar

El concepto de la computación concurrente suele ser confundido con el concepto de la computación paralela, las cuales están relacionadas pero son distintas.[2][3]​ Aunque ambas pueden ser descritas como "múltiples procesos ejecutándose dentro del mismo periodo de tiempo". En la computación paralela, la ejecución ocurre en el mismísimo instante físico: por ejemplo, en los diferentes procesadores o núcleos de una computadora multiprocesador. La computación paralela es imposible en una computadora con un procesador de un solo núcleo, la ejecución paralela es imposible ya que solo un cómputo puede procesarse en un instante dado (sin tener en cuenta casos especiales como por ejemplo la presencia de un coprocesador). En cambio, la computación concurrente consiste en que la vida de los procesos comparten el mismo tiempo, pero la ejecución de todos ellos no ocurre en el mismo instante.

Por ejemplo, múltiples procesos pueden ejecutarse en un solo procesador si se comparte el tiempo de ejecución, dándole a cada proceso una suma de tiempo limitada para ejecutarse. Solo un proceso se ejecuta en un instante dado, y si no completa su operación dentro de su tiempo, el proceso se coloca en pausa, y se le da lugar a otro proceso para iniciar o resumirse, hasta que le vuelva a tocar su tiempo.

Estructurar sistemas de software de manera que se componga por varias partes concurrentes y comunicadas entre sí es una muy buena forma de manejar la complejidad de una aplicación, más allá de si estas partes se ejecutan o no paralelamente.[4]

Coordinando el acceso a recursos compartidos

editar

Uno de los mayores desafíos a la hora de diseñar programas concurrentes es el control de concurrencia: Asegurar el orden correcto de las interacciones o comunicaciones entre diferentes ejecuciones computacionales, y coordinar el acceso a los recursos compartidos entre estas ejecuciones.[5]​ Los problemas potenciales incluyen el bloqueo mutuo, las condiciones de carrera y la inanición. Consideremos por ejemplo el siguiente algoritmo para retirar dinero de una cuenta bancaria, representada por el recurso compartido balance.

bool retirar(int monto_a_retirar)
{
    if (balance >= monto_a_retirar)
    {
        balance -= monto_a_retirar;
        return true;
    } 
    return false;
}

Supongamos que balance = 500 y que dos hilos concurrentes hacen las llamadas retirar(350) y retirar(300). Si en ambas operaciones la línea 3 se ejecuta antes que la línea 5, en ambas operaciones balance >= monto_a_retirar retornará verdadero, y se procederá a sustraer el monto a retirar. Sin embargo, como ambos procesos ejecutaran los retiros, la cantidad total retirada será mayor al balance original. Este tipo de problema se beneficia del uso del control de concurrencias, o de algoritmos no bloqueantes.

Implementación

editar

Existe una variedad de métodos a utilizar para implementar programas concurrentes, tales como implementar cada ejecución como un proceso del sistema operativo, o implementar los procesos computacionales como diferentes hilos del mismo proceso.

Interacción y comunicación

editar

En algunos sistemas de cómputo concurrentes, la comunicación entre los componentes concurrentes es escondida del programador (Por ejemplo con el uso de valores futuros) mientras en otros se debe manejar de manera explícita. La comunicación explícita se divide en dos clases:

Comunicación por memoria compartida

editar

Los componentes concurrentes se comunican alterando los valores de un área de memoria compartida (ejemplificado por Java y C#). Este estilo de programación concurrente suele necesitar algún tipo de bloqueo (Por ejemplo, mutex o semáforos) para coordinar entre hilos. Un programa que implementa correctamente alguno de estos es llamado thread-safe.

Comunicación a través de mensajes

editar

Los componentes concurrentes se comunican a través de paso de mensajes (ejemplificado por MPI, Go, Scala, Erlang y Occam). El cambio de mensajes se puede realizar asincrónicamente, o puede utilizar una manera sincrona en la cual quien envia el mensaje se bloquea a si mismo hasta que el mismo es recibido.

Ambos tipos tienen diferentes características en cuanto a rendimiento. Típicamente (pero no siempre) el uso de memoria por proceso y el trabajo para cambiar de tarea suele ser menor en un sistema de paso de mensajes, pero el tiempo de procesamiento para pasar un mensaje es mayor que una llamada a procedimiento. Estas diferencias suelen ser muy menores comparadas con otros factores visibles en un programa del mundo real.

Historia

editar

La computación concurrente apareció en el trabajo temprano en ferrocarriles y en la telegrafía, desde el siglo XIX y hasta los comienzos del 20, con algunos términos aún permaneciendo hoy en día, como por ejemplo "semáforos". Estos aparecieron para contestar ciertas preguntas, como manejar múltiples trenes en el mismo sistema ferroviario (evitando colisiones y maximizando la eficiencia) y como controlar múltiples transmisiones en el mismo conjunto de cables (maximizando eficiencia), como por ejemplo la multiplexión por división de tiempo, que data hasta los años 1870s.

El primer estudio académico de algoritmos concurrentes comenzó en la década del 60, con Dijkstra acreditado con haber escrito la primera documentación en esta área, identificando y resolviendo excluciones mutuas.[6]

Referencias

editar
  1. Operating System Concepts 9th edition, Abraham Silberschatz. "Chapter 4: Threads" (Conceptos de los Sistemas Operativos, 9a edición, capitulo 4)
  2. «Concurrency is not Parallelism». talks.golang.org. Consultado el 31 de agosto de 2020. 
  3. «Parallelism vs. Concurrency - HaskellWiki». wiki.haskell.org. Consultado el 31 de agosto de 2020. 
  4. Schneider, Fred B. (1997). «1». On Concurrent Programming (en inglés). Springer. ISBN 9780387949420. 
  5. Ben-Ari (2006). Addison-Wesley, ed. Principles of Concurrent and Distributed Programming (2nd ed.). ISBN 978-0-321-31283-9. 
  6. Dijkstra, E. W. (1 de septiembre de 1965). «Solution of a problem in concurrent programming control». Communications of the ACM 8 (9): 569. ISSN 0001-0782. doi:10.1145/365559.365617. Consultado el 31 de agosto de 2020.