Ciencias de la computación

Concurrencia y paralelismo: diferencias y concurrencia en un núcleo

La concurrencia organiza varias tareas alternándolas; el paralelismo ejecuta varias al mismo tiempo mediante hardware. Aquí resumimos por qué la concurrencia funciona en un solo núcleo y qué significa Swift Concurrency.

4 min de lectura
Imagen de portada de Concurrencia y paralelismo: diferencias y concurrencia en un núcleo

Concurrencia (concurrency) y paralelismo (parallelism) se parecen tanto en coreano como en inglés, por lo que suelen confundirse.

Incluso cuando las CPU tenían un solo núcleo, los ordenadores reproducían música mientras editábamos documentos. ¿Cómo podía una máquina que físicamente solo hace una cosa a la vez hacer varias «simultáneamente»?

Si puedes responder esta pregunta, ya distingues ambos conceptos. Si no, este artículo te ayudará a aclarar exactamente ese punto.

Lo mismo ocurre con nombres como Swift Concurrency y la cola concurrente de GCD (Grand Central Dispatch). Esta distinción es la base para entenderlos correctamente.

Este es el resumen clave.

  1. Concurrencia: estructura lógica que alterna varias tareas para «gestionarlas a la vez».
  2. Paralelismo: varias tareas se ejecutan físicamente en el mismo instante. Es un problema de hardware.
  3. La concurrencia es posible en un solo núcleo. El paralelismo requiere varios núcleos.
  4. La concurrencia se construye con la estructura del código; el paralelismo es el resultado de que el hardware aproveche esa estructura.

Entenderlo con una sola cafetera

Imagina una cafetería con un solo barista (un núcleo).

Llegan dos pedidos. Mientras extrae el espresso del cliente A, el barista calienta la leche del cliente B; al terminar la extracción, acaba la taza de A y vuelve a B. Ambos pedidos están «en curso al mismo tiempo», pero, en cualquier instante, el barista solo hace una cosa.

Eso es la concurrencia: dividir las tareas en partes pequeñas, alternarlas y gestionar varias a la vez.

Si contratas a otro barista (dos núcleos), los dos pedidos se preparan realmente en el mismo instante. Eso es el paralelismo.

Lo importante es esto: incluso con un solo barista funciona la «forma de operar alternando pedidos»; cuando hay dos, esa misma forma se amplía a una ejecución física simultánea.


Llevémoslo al ordenador

La multitarea de la era de un solo núcleo era exactamente una cafetería con un barista.

El sistema operativo asignaba la CPU alternativamente a cada proceso en intervalos de decenas de milisegundos (multiplexación temporal). Por eso, para las personas, la música y la edición de documentos parecían ejecutarse a la vez.

Diagrama que contrasta la multiplexación temporal en un solo núcleo con la ejecución simultánea en varios núcleos
Un solo núcleo alterna las tareas (multiplexación temporal); varios núcleos las ejecutan en el mismo instante

Con varios núcleos se añadió el paralelismo. Ahora, cuatro núcleos pueden ejecutar realmente cuatro hilos en el mismo instante.

Podemos resumir la relación así.

  • Solo paralelismo, sin concurrencia: tareas independientes, una por núcleo.
  • Concurrencia sin paralelismo: multiplexación temporal en un solo núcleo.
  • Concurrencia + paralelismo: la base de la computación moderna. Varios núcleos ejecutan las tareas divididas.

El título de una famosa charla de Rob Pike, creador del lenguaje Go, resume esta distinción en una frase.

«La concurrencia no es paralelismo (Concurrency is not parallelism).»

Distinguió la concurrencia como «gestionar varias tareas (dealing with)» y el paralelismo como «hacer varias tareas (doing)».


Qué significa el nombre Swift Concurrency

Desde esta perspectiva, es preciso que Apple llamara Swift «Concurrency» al modelo async/await.

async/await, Task y actor son herramientas para expresar «cómo dividir las tareas, dónde suspenderlas y en qué orden reanudarlas». Es decir, son recursos del lenguaje para diseñar estructuras concurrentes.

El runtime y el planificador del sistema deciden en cuántos núcleos se ejecuta esa estructura en paralelo. El desarrollador declara la estructura (concurrencia) y el sistema se encarga de la ejecución paralela.

async let a = fetchProfile()   // Declarar una estructura que «puede avanzar simultáneamente»
async let b = fetchFeed()
let result = try await (a, b)  // El sistema decide si se ejecuta en paralelo

Aunque crees 100 tareas con TaskGroup, con 8 núcleos solo 8 pueden ejecutarse al mismo tiempo. Las demás se alternan. Dentro de una estructura concurrente, el paralelismo está limitado por el hardware.

Ilustración de un planificador que distribuye 100 fichas de tareas entre 8 ranuras de núcleo
Al declarar una estructura concurrente, el sistema decide si habrá ejecución paralela

En una frase para la entrevista

«La concurrencia es una estructura lógica que gestiona varias tareas alternándolas, mientras que el paralelismo ejecuta varias tareas físicamente en el mismo instante. Por eso la concurrencia es posible en un solo núcleo, pero el paralelismo no.»

También conviene preparar las preguntas de seguimiento: «¿Cómo era posible la multitarea en un solo núcleo?» (multiplexación temporal) y «¿El código concurrente siempre se ejecuta en paralelo?» (No; lo decide el planificador).


Resumen

  • La concurrencia divide las tareas, las alterna y gestiona varias a la vez. Es un concepto lógico.
  • El paralelismo ejecuta varias tareas físicamente en el mismo instante. Es un concepto de hardware.
  • La concurrencia (multiplexación temporal) es posible en un solo núcleo, pero el paralelismo no.
  • Rob Pike: la concurrencia consiste en «gestionar» varias tareas; el paralelismo, en «hacerlas».
  • Swift Concurrency declara la estructura concurrente; el sistema decide la ejecución paralela real.
  • Analogía de la cafetera: un barista que alterna tareas representa la concurrencia; dos que trabajan juntos, el paralelismo.