Ejecución de varios cálculos durante períodos de tiempo superpuestos
La computación concurrente es una forma de computación en la que varios cálculos se ejecutan simultáneamente (durante períodos de tiempo superpuestos) en lugar de hacerlo de manera secuencial (uno se completa antes de que comience el siguiente).
Se trata de una propiedad de un sistema (ya sea un programa , un ordenador o una red ) en la que existe un punto de ejecución independiente o "hilo de control" para cada proceso. Un sistema concurrente es aquel en el que un cálculo puede avanzar sin esperar a que se completen todos los demás cálculos. [1]
La computación concurrente es una forma de programación modular . En su paradigma, un cálculo general se divide en subcálculos que pueden ejecutarse simultáneamente. Entre los pioneros en el campo de la computación concurrente se incluyen Edsger Dijkstra , Per Brinch Hansen y CAR Hoare . [2]
Introducción
El concepto de computación concurrente se confunde frecuentemente con el concepto relacionado pero distinto de computación paralela , [3] [4] aunque ambos pueden describirse como "múltiples procesos ejecutándose durante el mismo período de tiempo ". En la computación paralela, la ejecución ocurre en el mismo instante físico: por ejemplo, en procesadores separados de una máquina multiprocesador , con el objetivo de acelerar los cálculos; la computación paralela es imposible en un solo procesador ( de un núcleo ), ya que solo puede ocurrir un cálculo en cualquier instante (durante cualquier ciclo de reloj único). [a] Por el contrario, la computación concurrente consiste en la superposición de las duraciones de los procesos , pero la ejecución no ocurre en el mismo instante. El objetivo aquí es modelar procesos que ocurren simultáneamente, como múltiples clientes que acceden a un servidor al mismo tiempo. Estructurar sistemas de software como compuestos de múltiples partes concurrentes y comunicantes puede ser útil para abordar la complejidad, independientemente de si las partes se pueden ejecutar en paralelo. [5] : 1
Por ejemplo, los procesos concurrentes se pueden ejecutar en un núcleo intercalando los pasos de ejecución de cada proceso a través de segmentos de tiempo compartido : solo se ejecuta un proceso a la vez y, si no se completa durante su segmento de tiempo, se detiene , comienza o se reanuda otro proceso y, más tarde, se reanuda el proceso original. De esta manera, varios procesos se encuentran en la mitad de su ejecución en un solo instante, pero solo se está ejecutando un proceso en ese instante. [ cita requerida ]
Los cálculos concurrentes se pueden ejecutar en paralelo, [3] [6] por ejemplo, asignando cada proceso a un procesador o núcleo de procesador separado, o distribuyendo un cálculo a través de una red.
El momento exacto en que se ejecutan las tareas en un sistema concurrente depende de la programación , y no siempre es necesario que las tareas se ejecuten simultáneamente. Por ejemplo, dadas dos tareas, T1 y T2: [ cita requerida ]
- T1 puede ejecutarse y finalizarse antes que T2 o viceversa (serial y secuencial)
- T1 y T2 pueden ejecutarse alternativamente (en serie y concurrentemente)
- T1 y T2 pueden ejecutarse simultáneamente en el mismo instante de tiempo (paralelo y concurrente)
La palabra "secuencial" se utiliza como antónimo tanto de "concurrente" como de "paralelo"; cuando se distinguen explícitamente, concurrente/secuencial y paralelo/serial se utilizan como pares opuestos. Una programación en la que las tareas se ejecutan de a una por vez (en serie, sin paralelismo), sin intercalar (secuencialmente, sin concurrencia: ninguna tarea comienza hasta que finaliza la tarea anterior) se denomina programación serial . Un conjunto de tareas que se pueden programar en serie es serializable , lo que simplifica el control de la concurrencia . [ cita requerida ]
Coordinar el acceso a recursos compartidos
El principal desafío en el diseño de programas concurrentes es el control de la concurrencia : asegurar la secuencia correcta de las interacciones o comunicaciones entre diferentes ejecuciones computacionales y coordinar el acceso a los recursos que se comparten entre las ejecuciones. [6] Los problemas potenciales incluyen condiciones de carrera , bloqueos y escasez de recursos . Por ejemplo, considere el siguiente algoritmo para realizar retiros de una cuenta corriente representada por el recurso compartido balance
:
bool retirar ( int retirar ) { si ( saldo >= retiro ) { saldo -= retiro ; devuelve verdadero ; } devuelve falso ; }
Supongamos que balance = 500
, y dos subprocesos simultáneos realizan las llamadas withdraw(300)
y withdraw(350)
. Si la línea 3 de ambas operaciones se ejecuta antes que la línea 5, ambas operaciones encontrarán que balance >= withdrawal
se evalúa como true
, y la ejecución procederá a restar el monto del retiro. Sin embargo, dado que ambos procesos realizan sus retiros, el monto total retirado terminará siendo mayor que el saldo original. Este tipo de problemas con recursos compartidos se benefician del uso de control de concurrencia o algoritmos no bloqueantes .
Ventajas
Las ventajas de la computación concurrente incluyen:
- Mayor rendimiento del programa: la ejecución paralela de un programa simultáneo permite que la cantidad de tareas completadas en un tiempo determinado aumente proporcionalmente a la cantidad de procesadores según la ley de Gustafson.
- Alta capacidad de respuesta para entrada/salida: los programas que requieren un uso intensivo de entrada/salida generalmente esperan a que se completen las operaciones de entrada o salida. La programación concurrente permite utilizar el tiempo que se dedicaría a esperar para otra tarea. [ cita requerida ]
- Estructura de programa más apropiada: algunos problemas y dominios de problemas son adecuados para ser representados como tareas o procesos concurrentes. [ cita requerida ]
Modelos
Introducidas en 1962, las redes de Petri fueron un intento temprano de codificar las reglas de ejecución concurrente. La teoría de flujo de datos se basó más tarde en ellas y se crearon arquitecturas de flujo de datos para implementar físicamente las ideas de la teoría de flujo de datos. A fines de la década de 1970, se desarrollaron cálculos de procesos como el cálculo de sistemas de comunicación (CCS) y los procesos secuenciales de comunicación (CSP) para permitir el razonamiento algebraico sobre sistemas compuestos por componentes que interactúan. El cálculo π agregó la capacidad de razonar sobre topologías dinámicas.
Los autómatas de entrada/salida se introdujeron en 1987.
También se han desarrollado lógicas como TLA+ de Lamport y modelos matemáticos como trazas y diagramas de eventos de actor para describir el comportamiento de sistemas concurrentes.
La memoria transaccional del software toma prestado de la teoría de bases de datos el concepto de transacciones atómicas y las aplica a los accesos a la memoria.
Modelos de consistencia
Los lenguajes de programación concurrente y los programas multiprocesador deben tener un modelo de consistencia (también conocido como modelo de memoria). El modelo de consistencia define reglas sobre cómo se producen las operaciones en la memoria de la computadora y cómo se producen los resultados.
Uno de los primeros modelos de consistencia fue el modelo de consistencia secuencial de Leslie Lamport . La consistencia secuencial es la propiedad de un programa de que su ejecución produce los mismos resultados que un programa secuencial. En concreto, un programa es secuencialmente consistente si "los resultados de cualquier ejecución son los mismos que si las operaciones de todos los procesadores se ejecutaran en un orden secuencial, y las operaciones de cada procesador individual aparecen en esta secuencia en el orden especificado por su programa". [8]
Implementación
Se pueden utilizar varios métodos diferentes para implementar programas concurrentes, como implementar cada ejecución computacional como un proceso del sistema operativo o implementar los procesos computacionales como un conjunto de subprocesos dentro de un solo proceso del sistema operativo.
Interacción y comunicación
En algunos sistemas de computación concurrente, la comunicación entre los componentes concurrentes está oculta al programador (por ejemplo, mediante el uso de futuros ), mientras que en otros debe gestionarse explícitamente. La comunicación explícita se puede dividir en dos clases:
- Comunicación de memoria compartida
- Los componentes concurrentes se comunican modificando el contenido de las ubicaciones de memoria compartida (ejemplificados por Java y C# ). Este estilo de programación concurrente generalmente necesita el uso de alguna forma de bloqueo (por ejemplo, mutexes , semáforos o monitores ) para coordinar entre subprocesos. Un programa que implementa adecuadamente cualquiera de estos se dice que es seguro para subprocesos .
- Comunicación mediante paso de mensajes
- Los componentes concurrentes se comunican intercambiando mensajes (ejemplificados por MPI , Go , Scala , Erlang y occam ). El intercambio de mensajes puede llevarse a cabo de forma asincrónica, o puede utilizar un estilo de "encuentro" sincrónico en el que el remitente se bloquea hasta que se recibe el mensaje. El paso de mensajes asincrónico puede ser confiable o no confiable (a veces denominado "enviar y rezar"). La concurrencia de paso de mensajes tiende a ser mucho más fácil de razonar que la concurrencia de memoria compartida, y generalmente se considera una forma más robusta de programación concurrente. [ cita requerida ] Hay una amplia variedad de teorías matemáticas disponibles para comprender y analizar los sistemas de paso de mensajes, incluido el modelo de actor y varios cálculos de procesos . El paso de mensajes se puede implementar de manera eficiente a través del multiprocesamiento simétrico , con o sin coherencia de caché de memoria compartida .
La memoria compartida y la concurrencia en el paso de mensajes tienen diferentes características de rendimiento. Normalmente (aunque no siempre), la sobrecarga de memoria por proceso y la sobrecarga de conmutación de tareas son menores en un sistema de paso de mensajes, pero la sobrecarga del paso de mensajes es mayor que en una llamada a un procedimiento. Estas diferencias suelen verse superadas por otros factores de rendimiento.
Historia
La computación concurrente surgió a partir de trabajos anteriores sobre ferrocarriles y telegrafía , del siglo XIX y principios del XX, y algunos términos datan de este período, como los semáforos. Estos surgieron para abordar la cuestión de cómo manejar múltiples trenes en el mismo sistema ferroviario (evitando colisiones y maximizando la eficiencia) y cómo manejar múltiples transmisiones en un conjunto determinado de cables (mejorando la eficiencia), como por ejemplo mediante multiplexación por división de tiempo (década de 1870).
El estudio académico de los algoritmos concurrentes comenzó en la década de 1960, y a Dijkstra (1965) se le atribuye el primer artículo en este campo, identificando y resolviendo la exclusión mutua . [9]
Predominio
La concurrencia es omnipresente en la informática, y se da desde hardware de bajo nivel en un solo chip hasta redes mundiales. A continuación se ofrecen algunos ejemplos.
A nivel de lenguaje de programación:
A nivel del sistema operativo:
A nivel de red, los sistemas en red generalmente son concurrentes por su naturaleza, ya que constan de dispositivos separados.
Lenguajes que admiten programación concurrente
Los lenguajes de programación concurrente son lenguajes de programación que utilizan construcciones de lenguaje para la concurrencia . Estas construcciones pueden implicar subprocesamiento múltiple , compatibilidad con computación distribuida , paso de mensajes , recursos compartidos (incluida la memoria compartida ) o futuros y promesas . Dichos lenguajes a veces se describen como lenguajes orientados a la concurrencia o lenguajes de programación orientados a la concurrencia (COPL). [10]
En la actualidad, los lenguajes de programación más utilizados que tienen estructuras específicas para la concurrencia son Java y C# . Ambos lenguajes utilizan fundamentalmente un modelo de concurrencia de memoria compartida, con bloqueo proporcionado por monitores (aunque los modelos de paso de mensajes pueden y han sido implementados sobre el modelo de memoria compartida subyacente). De los lenguajes que utilizan un modelo de concurrencia de paso de mensajes, Erlang es probablemente el más utilizado en la industria en la actualidad. [ cita requerida ]
Muchos lenguajes de programación concurrente se han desarrollado más como lenguajes de investigación (por ejemplo, Pict ) que como lenguajes para uso en producción. Sin embargo, lenguajes como Erlang , Limbo y Occam han tenido un uso industrial en varias ocasiones durante los últimos 20 años. A continuación, se incluye una lista no exhaustiva de lenguajes que utilizan o proporcionan facilidades de programación concurrente:
- Ada : propósito general, con soporte nativo para paso de mensajes y concurrencia basada en monitor
- Alef —concurrente, con subprocesos y paso de mensajes, para programación de sistemas en versiones tempranas de Plan 9 de Bell Labs
- Alice : extensión de Standard ML , agrega soporte para concurrencia a través de futuros
- Ateji PX : extensión de Java con primitivas paralelas inspiradas en el cálculo π
- Axum : específico del dominio, concurrente, basado en el modelo de actor y .NET Common Language Runtime que utiliza una sintaxis similar a C
- BMDFM : máquina de flujo de datos modular binario
- C++ — bibliotecas de soporte de subprocesos y corrutinas [11] [12]
- Cω (C omega): para investigación, extiende C#, utiliza comunicación asincrónica
- C# : admite computación concurrente mediante bloqueo, rendimiento y, desde la versión 5.0, también se introdujeron las palabras clave async y await
- Clojure : dialecto moderno y funcional de Lisp en la plataforma Java
- Concurrent Clean : programación funcional, similar a Haskell
- Recopilaciones concurrentes (CnC): logra un paralelismo implícito independiente del modelo de memoria al definir explícitamente el flujo de datos y el control.
- Haskell concurrente : lenguaje funcional puro y perezoso que opera procesos concurrentes en memoria compartida
- ML concurrente : extensión concurrente del ML estándar
- Pascal concurrente —por Per Brinch Hansen
- Curry
- D — lenguaje de programación de sistemas multiparadigma con soporte explícito para programación concurrente ( modelo de actor )
- E —usa promesas para evitar puntos muertos
- ECMAScript : utiliza promesas para operaciones asincrónicas
- Eiffel —a través de su mecanismo SCOOP basado en los conceptos de Diseño por Contrato
- Elixir : lenguaje de metaprogramación dinámico y funcional que se ejecuta en la máquina virtual Erlang.
- Erlang : utiliza el paso de mensajes sincrónico o asincrónico sin memoria compartida
- FAUST: compilador funcional en tiempo real para procesamiento de señales que proporciona paralelización automática a través de OpenMP o un programador específico que roba trabajo
- Fortran : los coarrays y la concurrencia son parte del estándar Fortran 2008
- Go —para programación de sistemas, con un modelo de programación concurrente basado en CSP
- Haskell: lenguaje de programación funcional concurrente y paralela [13]
- Hume : funcional, concurrente, para entornos de espacio y tiempo acotados donde los procesos de autómatas se describen mediante patrones de canales sincrónicos y paso de mensajes.
- Io: concurrencia basada en actores
- Janus —presenta distintos solicitantes y emisores de variables lógicas, canales de bolsa; es puramente declarativo
- Java —clase de subproceso o interfaz Runnable
- Julia —"primitivas de programación concurrente: tareas, espera asincrónica, canales". [14]
- JavaScript —a través de trabajadores web , en un entorno de navegador, promesas y devoluciones de llamadas .
- JoCaml : extensión de OCaml basada en canales concurrentes y distribuidos que implementa el cálculo de unión de procesos
- Únase a Java —concurrente, basado en el lenguaje Java
- Joule: basado en flujo de datos, se comunica mediante el paso de mensajes
- Joyce : enseñanza simultánea, desarrollada en Concurrent Pascal con características de CSP por Per Brinch Hansen
- LabVIEW : gráfico, flujo de datos, las funciones son nodos en un gráfico, los datos son cables entre los nodos; incluye lenguaje orientado a objetos
- Limbo —pariente de Alef , para la programación del sistema en Inferno (sistema operativo)
- BASIC de Locomotive : la variante de BASIC de Amstrad contiene comandos EVERY y AFTER para subrutinas concurrentes
- MultiLisp : variante de Scheme ampliada para soportar paralelismo
- Modula-2 —para programación de sistemas, por N. Wirth como sucesor de Pascal con soporte nativo para corrutinas
- Modula-3 : miembro moderno de la familia Algol con amplio soporte para subprocesos, mutexes y variables de condición
- Newsqueak —para investigación, con canales como valores de primera clase; predecesor de Alef
- Occam —fuertemente influenciado por los procesos secuenciales de comunicación (CSP)
- ooRexx: intercambio de mensajes basado en objetos para comunicación y sincronización
- Orco : altamente concurrente, no determinista, basado en el álgebra de Kleene
- Oz-Mozart : multiparadigma, admite concurrencia de estado compartido y paso de mensajes, y futuros
- ParaSail: orientado a objetos, paralelo, libre de punteros, condiciones de carrera
- PHP : soporte para subprocesos múltiples con extensión paralela que implementa el paso de mensajes inspirado en Go [15]
- Pict : esencialmente una implementación ejecutable del cálculo π de Milner
- Raku incluye clases para subprocesos, promesas y canales por defecto [16]
- Python utiliza paralelismo basado en subprocesos y paralelismo basado en procesos [17]
- Reia : utiliza el paso de mensajes asincrónico entre objetos que no comparten nada
- Red/System —para programación de sistemas, basado en Rebol
- Rust : para programación de sistemas, utiliza paso de mensajes con semántica de movimiento, memoria inmutable compartida y memoria mutable compartida. [18]
- Scala : propósito general, diseñado para expresar patrones de programación comunes de una manera concisa, elegante y segura en términos de tipos.
- SequenceL : funcional de propósito general, los principales objetivos de diseño son la facilidad de programación, la claridad y legibilidad del código y la paralelización automática para el rendimiento en hardware multinúcleo y demostrablemente libre de condiciones de carrera .
- SR —para investigación
- SuperPascal : concurrente, para la enseñanza, desarrollado sobre Concurrent Pascal y Joyce por Per Brinch Hansen
- Swift : soporte integrado para escribir código asincrónico y paralelo de forma estructurada [19]
- Unicon —para investigación
- TNSDL : para desarrollar intercambios de telecomunicaciones, utiliza el paso de mensajes asincrónicos
- Lenguaje de descripción de hardware VHSIC ( VHDL ): IEEE STD-1076
- XC : subconjunto extendido de concurrencia del lenguaje C desarrollado por XMOS , basado en la comunicación de procesos secuenciales , construcciones integradas para E/S programable
Muchos otros lenguajes proporcionan soporte para concurrencia en forma de bibliotecas, en niveles aproximadamente comparables con la lista anterior.
Véase también
Notas
- ^ Esto no tiene en cuenta el paralelismo interno del núcleo de un procesador, como la segmentación o las instrucciones vectorizadas. Una máquina con un solo núcleo y un solo procesador puede ser capaz de lograr cierto paralelismo, como con un coprocesador , pero el procesador por sí solo no lo es.
Referencias
- ^ Conceptos de sistemas operativos , novena edición, Abraham Silberschatz. "Capítulo 4: Subprocesos"
- ^ Hansen, Por Brinch, ed. (2002). El origen de la programación concurrente. doi :10.1007/978-1-4757-3472-0. ISBN 978-1-4419-2986-0.S2CID 44909506 .
- ^ ab Pike, Rob (11 de enero de 2012). "La concurrencia no es paralelismo". Conferencia de Waza , 11 de enero de 2012. Recuperado de http://talks.golang.org/2012/waza.slide (diapositivas) y http://vimeo.com/49718712 (vídeo).
- ^ "Paralelismo vs. Concurrencia". Wiki Haskell .
- ^ Schneider, Fred B. (6 de mayo de 1997). Sobre programación concurrente . Springer. ISBN 9780387949420.
- ^ ab Ben-Ari, Mordechai (2006). Principios de programación concurrente y distribuida (2.ª ed.). Addison-Wesley. ISBN 978-0-321-31283-9.
- ^ Lamport, Leslie (1 de septiembre de 1979). "Cómo crear una computadora multiprocesador que ejecute correctamente programas multiproceso". IEEE Transactions on Computers . C-28 (9): 690–691. doi :10.1109/TC.1979.1675439. S2CID 5679366.
- ^ "PODC Influential Paper Award: 2002", Simposio ACM sobre Principios de Computación Distribuida , consultado el 24 de agosto de 2009
- ^ Armstrong, Joe (2003). "Creación de sistemas distribuidos fiables en presencia de errores de software" (PDF) . Archivado desde el original (PDF) el 15 de abril de 2016.
- ^ "Encabezado de biblioteca estándar <thread> (C++11)". en.cppreference.com . Consultado el 2024-10-03 .
- ^ "Encabezado de biblioteca estándar <coroutine> (C++20)". en.cppreference.com . Consultado el 2024-10-03 .
- ^ Marlow, Simon (2013) Programación paralela y concurrente en Haskell: técnicas para programación multinúcleo y multiproceso ISBN 9781449335946
- ^ "Programación concurrente y paralela en Julia — JuliaCon India 2015 — HasGeek Talkfunnel". juliacon.talkfunnel.com . Archivado desde el original el 18 de octubre de 2016.
- ^ "PHP: paralelo - Manual" www.php.net . Consultado el 3 de octubre de 2024 .
- ^ "Concurrencia". docs.perl6.org . Consultado el 24 de diciembre de 2017 .
- ^ Documentación » La biblioteca estándar de Python » Ejecución concurrente
- ^ Blum, Ben (2012). "Estado mutable compartido con seguridad de tipos" . Consultado el 14 de noviembre de 2012 .
- ^ "Concurrencia". 2022 . Consultado el 15 de diciembre de 2022 .
Fuentes
- Patterson, David A.; Hennessy, John L. (2013). Organización y diseño de computadoras: la interfaz hardware/software . Serie Morgan Kaufmann sobre arquitectura y diseño de computadoras (5.ª ed.). Morgan Kaufmann. ISBN 978-0-12407886-4.
Lectura adicional
- Dijkstra, EW (1965). "Solución de un problema de control de programación concurrente". Comunicaciones de la ACM . 8 (9): 569. doi : 10.1145/365559.365617 . S2CID 19357737.
- Herlihy, Maurice (2008) [2008]. El arte de la programación multiprocesador . Morgan Kaufmann. ISBN 978-0123705914.
- Downey, Allen B. (2005) [2005]. El pequeño libro de los semáforos (PDF) . Green Tea Press. ISBN 978-1-4414-1868-5Archivado desde el original (PDF) el 4 de marzo de 2016. Consultado el 21 de noviembre de 2009 .
- Filman, Robert E.; Daniel P. Friedman (1984). Computación coordinada: herramientas y técnicas para software distribuido. Nueva York: McGraw-Hill. pág. 370. ISBN 978-0-07-022439-1.
- Leppäjärvi, Jouni (2008). Un estudio pragmático e históricamente orientado sobre la universalidad de las primitivas de sincronización (PDF) . Universidad de Oulu. Archivado desde el original (PDF) el 2017-08-30 . Consultado el 2012-09-13 .
- Taubenfeld, Gadi (2006). Algoritmos de sincronización y programación concurrente. Pearson / Prentice Hall. pág. 433. ISBN 978-0-13-197259-9.
Enlaces externos
- Medios relacionados con Programación concurrente en Wikimedia Commons
- Biblioteca virtual de sistemas concurrentes