Protože konkurence je obtížná

Souběžnost je schopnost programu řídit více „probíhajících“ úkolů. současně — ne nutně paralelně. The rovnoběžnost to je provedení současně na více fyzických jádrech. Záměna mezi těmito dvěma pojmy je zdrojem mnoha chyby a špatná architektonická rozhodnutí.

V roce 2026 existuje pět hlavních soutěžních modelů, z nichž každý má jiné kompromisy. Ne existuje absolutně „nejlepší“ model: výběr závisí na typu zátěže (I/O-bound vs CPU-bound), jazyk, ekosystém a požadavky na latenci.

Model 1: OS Threads (Java, C++)

Nejtradičnější model: každá souběžná jednotka je a vlákno operačního systému. Jádro se stará o plánování, přepínání kontextu a komunikaci mezi vlákny prostřednictvím sdílené paměti chráněné mutexem.

// 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: Jednoduchý mentální model, automatické využití více jader, vyspělé knihovny

Proti: Drahá vlákna OS (1 MB+ zásobník, režie přepínání), podmínky závodů sdílené paměti, škálování omezené na tisíce vláken

Když: Práce vázaná na CPU, Java/C++ s fondem vláken, smíšená zatížení

Vzor 2: Smyčka událostí s jedním vláknem (Node.js, JavaScript)

JavaScript je jednovláknový: existuje pouze jedno vlákno provádění a smyčka událostí spravuje zpětná volání. Asynchronní I/O (síť, souborový systém) je delegován na operační systém přes libuv a dokončené operace jsou umístěny do fronty zpětných volání.

// 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: Žádné rasové podmínky (jedno vlákno), velmi vysoká souběžnost pro I/O-vázané, obrovský ekosystém npm

Proti: Vázané na CPU blokuje vše, peklo zpětného volání (zmírněno async/wait), Worker Threads pro skutečný paralelismus

Když: API server s mnoha souběžnými I/O požadavky, aplikace v reálném čase, vrstva BFF

Model 3: Goroutine a Channel (Go)

Jdi nářadí Komunikující sekvenční procesy (CSP): ultralehké goroutines (počáteční zásobník 2 kB, dynamicky roste) komunikovaný prostřednictvím typovaných kanálů. Mantra Go: "Nekomunikujte sdílením paměti, sdílejte paměť komunikací."

// 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: Ultralehké goroutiny (miliony životaschopných), kanály zabraňují závodním podmínkám, runtime řídí plánování

Proti: Model CSP vyžaduje učení, rutinní únik, pokud kanál není uzavřen, žádná generika před Go 1.18

Když: Backendové služby s vysokou I/O souběžností, datové kanály, nástroje CLI

Vzor 4: Async/Await (Python, Rust)

Async/wait je družstevní soutěž: úkoly se výslovně vzdát ovládání na I/O čekacích bodech (await). Na rozdíl od smyčky událostí JavaScriptu (vestavěný runtime), Python a Rust vyžadují explicitní runtime (asyncio, Tokio).

// 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: Explicitní kontrola nad čekacími body, žádné závodní podmínky ve sdíleném zásobníku, nulové náklady v Rustu

Proti: "Asynchronní nemoc" (každá funkce musí být asynchronní, pokud volá async), složitější ladění

Když: Python pro I/O-bound (web scraping, API volání), Rust pro vysoce výkonné systémy

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

Model herce je nejizolovanější: každý herec má svůj soukromý stav a komunikuje pouze prostřednictvím zpráv. Neexistuje žádná sdílená paměť, neexistují žádné mutexy – každý herec je nezávislý lehký proces.

// 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: Úplná izolace (zhroucení herce se nešíří), odolnost vůči chybám podle návrhu, nativně distribuováno

Proti: Režie serializace zpráv, ladění systémů s mnoha aktéry je složité

Když: Distribuované systémy odolné proti chybám, telco, hraní v reálném čase, internet věcí s miliony připojení

Srovnávací benchmark

Soutěž: Průvodce výběrem modelu

  • Mnoho I/O požadavků (webový server API): Go goroutine nebo smyčka událostí Node.js – obě se škálují na desítky tisíc souběžných
  • Vazba na CPU (ML inference, kódování): Vlákna operačního systému Java nebo Go s pracovními fondy – použijte všechna jádra
  • Ultra nízká latence (obchodování, hraní her): Rust Tokyo or Go – nulová režie za běhu
  • Distribuovaná odolnost proti chybám (telco, IoT): Elixir/Erlang Model herce — nativní strom dohledu
  • Datová věda, skriptování: Python asyncio pro I/O, multiprocessing pro CPU-bound
  • Podnikový tým Java: Java 21 Virtual Threads — stejný mentální model jako klasická vlákna, měřítka jako goroutine

Závěry

Neexistuje žádný univerzálně lepší model konkurence. Správná volba závisí na pracovní zátěž (I/O vs CPU), týmem (dovednosti a preference), ekosystémem (dostupné knihovny) a nefunkční požadavky (latence, propustnost, odolnost proti chybám).

Následující články série se podrobně ponoří do každého modelu: Začněme smyčka událostí JavaScriptu, nejvíce nepochopená součást Node.js.