Porovnávané modely souběžnosti: vlákna OS, smyčky událostí, goroutiny, herci a async/čekání
Definitivní mapa souběžných modelů: vlákna OS (Java), zelená vlákna/goroutina (Go), jednovláknová smyčka událostí (Node.js), model aktéra (Erlang/Elixir), async/await (Python/Rust). Kdy použít který, klady/zápory a srovnávací benchmarky.
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.







