Pentru că concurența este dificilă

Concurența este capacitatea unui program de a gestiona mai multe sarcini „în curs” în același timp — nu neapărat în paralel. The paralelism este execuția simultan pe mai multe nuclee fizice. Confuzia dintre cele două concepte este sursa multora bug-uri și decizii arhitecturale proaste.

Există cinci modele principale de competiție în 2026, fiecare cu compromisuri diferite. Nu există cel mai bun model absolut: alegerea depinde de tipul de încărcare de lucru (I/O-bound vs. Cerințele legate de CPU), limbaj, ecosistem și latență.

Model 1: fire de operare (Java, C++)

Cel mai tradițional model: fiecare unitate de concurență este a fir a sistemului de operare. Nucleul se ocupă de programare, comutări de context și comunicare între fire prin intermediul memoriei partajate protejate de mutex.

// Java: thread tradizionale vs virtual thread (Java 21)
// Thread OS tradizionale — costoso: ~1MB stack, scheduling kernel
Thread platformThread = new Thread(() -> {
    processRequest(); // blocca il thread OS durante I/O
});
platformThread.start();

// Virtual Thread (Java 21, Project Loom) — leggero: ~2KB stack iniziale
Thread virtualThread = Thread.ofVirtual().start(() -> {
    processRequest(); // blocca solo il virtual thread, non il carrier
});

// Un milione di virtual thread sono praticabili
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 1_000_000; i++) {
        executor.submit(() -> handleRequest());
    }
} // attende il completamento

Pro: Model mental simplu, folosește automat mai multe nuclee, biblioteci mature

Împotriva: Fire de operare scumpe (stivă de 1 MB+, overhead de comutare), condiții de concurență a memoriei partajate, scalare limitată la mii de fire

Când: Lucru legat de CPU, Java/C++ cu pool de fire, încărcări mixte

Model 2: buclă de evenimente cu un singur fir (Node.js, JavaScript)

JavaScript este cu un singur thread: există doar un fir de execuție și o buclă de evenimente care gestionează apelurile inverse. I/O asincron (rețea, sistem de fișiere) este delegat sistemului de operare via libuv iar operațiunile finalizate sunt plasate în coada de apel invers.

// Node.js: event loop in azione
// Tutto esegue sullo stesso thread — nessun race condition!

const http = require('http');

http.createServer((req, res) => {
    // Questa callback non blocca il thread
    fetchUserData(req.userId)
        .then(user => {
            return fetchOrders(user.id); // altra I/O non bloccante
        })
        .then(orders => {
            res.json({ user, orders });
        })
        .catch(err => res.status(500).json({ error: err.message }));
}).listen(3000);

// Async/await (zucchero sintattico sopra Promise):
async function handleRequest(req, res) {
    const user = await fetchUserData(req.userId);   // non blocca il thread
    const orders = await fetchOrders(user.id);       // non blocca il thread
    res.json({ user, orders });
}

Pro: Fără condiții de cursă (single-thread), concurență foarte mare pentru I/O-bound, ecosistem imens npm

Împotriva: Procesor-bound blochează totul, callback hell (atenuat de asincron/așteptare), Worker Threads pentru paralelism real

Când: Server API cu multe solicitări I/O concurente, aplicații în timp real, strat BFF

Model 3: Goroutine și Channel (Go)

Du-te unelte Comunicarea proceselor secvențiale (CSP): goroutine ultra-ușoare (stiva inițială de 2 KB, crește dinamic) comunicată prin canale tipizate. Mantra Go: „Nu comunica prin împărțirea memoriei; împărtășește memoria comunicând.”

// Go: goroutine e channel
package main

import (
    "fmt"
    "sync"
)

// Fan-out/Fan-in pattern con goroutine
func processItems(items []Item) []Result {
    results := make(chan Result, len(items))
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        go func(i Item) {           // avvia goroutine — ~2KB stack
            defer wg.Done()
            result := processItem(i)  // eseguito concorrentemente
            results <- result
        }(item)
    }

    // Chiudi il channel quando tutte le goroutine completano
    go func() {
        wg.Wait()
        close(results)
    }()

    // Raccoglie i risultati
    var collected []Result
    for r := range results {
        collected = append(collected, r)
    }
    return collected
}

// Channel per comunicazione sicura tra goroutine
func producer(ch chan<- int) {
    for i := 0; i < 10; i++ {
        ch <- i    // invia sul channel (blocca se pieno)
    }
    close(ch)
}

func consumer(ch <-chan int) {
    for v := range ch {     // riceve finché il channel è aperto
        fmt.Println(v)
    }
}

Pro: Goroutine ultra-ușoare (viabile de milioane), canalele previn condițiile de cursă, timpul de rulare gestionează programarea

Împotriva: Modelul CSP necesită învățare, scurgere goroutine dacă canalul nu este închis, fără generice înainte de Go 1.18

Când: Servicii de backend cu concurență mare I/O, conducte de date, instrumente CLI

Model 4: Async/Await (Python, Rust)

Async/wait este competiție cooperativă: sarcinile renunță în mod explicit la control la punctele de așteptare I/O (await). Spre deosebire de bucla de evenimente JavaScript (timpul de rulare încorporat), Python și Rust necesită un timp de rulare explicit (asyncio, Tokyo).

// Python: asyncio con TaskGroup (Python 3.11+)
import asyncio
import aiohttp

async def fetch_url(session: aiohttp.ClientSession, url: str) -> str:
    async with session.get(url) as response:
        return await response.text()

async def fetch_all_parallel(urls: list[str]) -> list[str]:
    async with aiohttp.ClientSession() as session:
        # TaskGroup garantisce che tutti i task completino o vengano cancellati
        async with asyncio.TaskGroup() as tg:
            tasks = [tg.create_task(fetch_url(session, url)) for url in urls]

    return [task.result() for task in tasks]

# Rust: Tokio async/await (zero-cost)
use tokio::time::{sleep, Duration};

async fn fetch_data(id: u64) -> String {
    sleep(Duration::from_millis(100)).await;  // simula I/O
    format!("data_{}", id)
}

#[tokio::main]
async fn main() {
    // Join concorrente senza allocazioni aggiuntive (zero-cost)
    let (r1, r2, r3) = tokio::join!(
        fetch_data(1),
        fetch_data(2),
        fetch_data(3),
    );
    println!("{}, {}, {}", r1, r2, r3);
}

Pro: Control explicit asupra punctelor de așteptare, fără condiții de cursă pe stiva partajată, cost zero în Rust

Împotriva: „Boala asincronă” (fiecare funcție trebuie să fie asincronă dacă apelează asincron), depanare mai complexă

Când: Python pentru I/O-bound (web scraping, apeluri API), Rust pentru sisteme de înaltă performanță

Model 5: Actor Model (Erlang/Elixir, Akka)

Modelul de actor este cel mai izolat: fiecare actor are propriul său stat privat și comunică doar prin mesaje. Nu există memorie partajată, nu există mutexuri - fiecare actor este un proces independent și ușor.

// Elixir: GenServer (actor model)
defmodule Counter do
  use GenServer

  # Interfaccia pubblica
  def start_link(initial \\ 0) do
    GenServer.start_link(__MODULE__, initial, name: __MODULE__)
  end

  def increment() do
    GenServer.call(__MODULE__, :increment)
  end

  def get_count() do
    GenServer.call(__MODULE__, :get)
  end

  # Implementazione (private)
  def init(initial), do: {:ok, initial}

  def handle_call(:increment, _from, count) do
    {:reply, count + 1, count + 1}   # reply, valore_risposta, nuovo_stato
  end

  def handle_call(:get, _from, count) do
    {:reply, count, count}
  end
end

# La BEAM VM può avere milioni di processi leggeri
# con supervisione automatica (OTP supervisor tree)

Pro: Izolare totală (accidentul actorului nu se propagă), toleranță la erori prin proiectare, distribuită nativ

Împotriva: Serializarea mesajelor, depanarea sistemelor cu mulți actori este complexă

Când: Sisteme distribuite tolerante la erori, telecomunicații, jocuri în timp real, IoT cu milioane de conexiuni

Benchmark comparativ

Concurs: Ghid de selecție a modelelor

  • Multe solicitări I/O (server web API): Goroutine sau bucla de evenimente Node.js - ambele se scalează la zeci de mii de concurente
  • Legat de CPU (inferență ML, codificare): fire de operare Java sau Go OS cu grupuri de lucrători — utilizați toate nucleele
  • Latență ultra-scăzută (trading, gaming): Rust Tokyo sau Go — zero overhead în timpul de rulare
  • Toleranță la erori distribuită (telco, IoT): Elixir/Erlang Actor model — arbore de supraveghere nativ
  • Știința datelor, scripting: Python asincron pentru I/O, multiprocesare pentru CPU-bound
  • Echipa de întreprindere Java: Java 21 Virtual Threads — același model mental ca firele clasice, scale ca goroutine

Concluzii

Nu există un model de competiție universal superior. Alegerea corectă depinde de sarcina de lucru (I/O vs CPU), de echipă (abilități și preferințe), de ecosistem (biblioteci disponibile) și cerințe nefuncționale (latență, debit, toleranță la erori).

Următoarele articole din serie aprofundează în detaliu fiecare model: Să începem cu bucla de evenimente JavaScript, cea mai neînțeleasă componentă a Node.js.

Următorul în serie JavaScript bucla eveniment →