Saltar al contenido principal

Concurrencia y Asincronía

Los modelos de concurrencia son uno de los mayores diferenciadores entre lenguajes de programación. Así es como los diferentes lenguajes manejan la programación asíncrona, los hilos y la ejecución concurrente.

Ejecución Asíncrona / Concurrente Básica​

// Promises
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("data"), 1000);
});
};

// Async/await
async function main() {
const result = await fetchData();
console.log(result);
}

// Promise.all (parallel)
const [a, b] = await Promise.all([
fetchData(),
fetchData(),
]);

// Promise.race (first to complete)
const fastest = await Promise.race([
fetchData(),
fetchData(),
]);

Seguridad de Hilos y Sincronización​

// JavaScript is single-threaded (event loop)
// No shared memory issues in main thread

// Web Workers (browser) / Worker Threads (Node.js)
const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js');
worker.postMessage({ data: 'hello' });
worker.on('message', (msg) => {
console.log('From worker:', msg);
});

// SharedArrayBuffer (shared memory)
const buffer = new SharedArrayBuffer(1024);
const view = new Int32Array(buffer);
Atomics.add(view, 0, 1); // Atomic operation

Puntos Clave​

  • Los modelos de concurrencia varían ampliamente -- Go usa goroutines con canales (estilo CSP): go func() { ch <- "hello" } y msg := <-ch. JavaScript, Python, Rust, C# y Swift usan async/await: async def fetch_data() en Python, async fn fetch_data() en Rust con tokio, async Task<string> FetchDataAsync() en C#. C, C++ y Java usan hilos tradicionales del sistema operativo con pthread_create, std::thread o Thread.start(), más mutexes para sincronización. Las goroutines son ligeras (millones posibles); los hilos son más pesados. Async/await es adecuado para trabajo limitado por E/S; los hilos son adecuados para paralelismo limitado por CPU. Elige Go para concurrencia simple; elige async/await para servidores con mucha E/S; elige hilos cuando necesites verdadero paralelismo y control.

  • La comunicación basada en canales es de primera clase en Go -- Los canales integrados de Go y select hacen que los patrones productor-consumidor sean idiomáticos: ch := make(chan string), ch <- "msg", msg := <-ch y select { case msg := <-ch1: ... case msg := <-ch2: ... }. Rust ofrece std::sync::mpsc::channel() y C# tiene Channel.CreateUnbounded<T>(), pero son características de biblioteca. El diseño de Go promueve "compartir memoria comunicándose" en lugar de mutexes. Si construyes pipelines, pools de workers o sistemas orientados a eventos, los canales de Go son los más ergonómicos; Rust y C# proporcionan un poder similar con más configuración.

  • Go y Rust sobresalen en concurrencia -- Las goroutines de Go son ligeras (pilas de 2KB, multiplexadas sobre hilos del SO) y fáciles de crear: go worker(id). El modelo de propiedad de Rust previene carreras de datos en tiempo de compilación: el compilador rechaza compartir estado mutable entre hilos sin sincronización. Por ejemplo, Arc<Mutex<i32>> en Rust asegura acceso compartido seguro. Go depende de canales y disciplina; Rust impone seguridad estáticamente. Elige Go cuando quieras concurrencia simple y rápida con mínima fricción; elige Rust cuando necesites máxima seguridad y rendimiento sin recolector de basura.

  • Swift introduce actores -- Los actores proporcionan estado compartido seguro entre hilos al serializar el acceso: actor Counter { private var count = 0; func increment() { count += 1 } } -- todos los métodos se ejecutan en serie, así que no hay carreras de datos. Llamas con await account.deposit(100). Esto reemplaza los patrones manuales de Mutex/Lock. Los actores de Swift se integran con async/await y la concurrencia estructurada. Si construyes sistemas concurrentes en Swift (p. ej., iOS/macOS), prefiere actores sobre bloqueo manual; para otros lenguajes, usa mutexes o diseños basados en canales.

  • JavaScript es de un solo hilo -- El bucle de eventos maneja la asincronía mediante callbacks y Promises: await fetch('/api') no bloquea el hilo; cede hasta que llega la respuesta. El verdadero paralelismo requiere Web Workers (navegador) o Worker Threads (Node.js) con postMessage para comunicación. El trabajo pesado de CPU (hashing, procesamiento de imágenes) bloquea el hilo principal; descárgalo a workers. Para servidores Node.js con mucha E/S, async/await es suficiente; para tareas limitadas por CPU, agrega workers o considera un lenguaje multi-hilo como Go o Rust.