Se il server è lento, aumentare i thread?
Quando sono entrato in azienda per la prima volta, il server API Spring Boot di cui ero responsabile rallentava ogni volta che c’era molto traffico. Poiché non conoscevo la causa, ho iniziato a cercare su Google.
“Risposta lenta del server Spring Boot” “Ottimizzazione delle prestazioni di Tomcat”
Come risultato della ricerca, i consigli più comuni trovati su blog e community erano semplici. ‘Aumenta la dimensione del pool di thread di Tomcat. La tua richiesta è in attesa perché non ci sono abbastanza lavoratori.’
Ho pensato: “Aha, siamo a corto di lavoratori!” Ho pensato semplicemente. Ho immediatamente aperto le impostazioni di application.yml e ho aumentato il numero di thread dal valore predefinito di 200 a 2.000. Secondo i miei calcoli, il numero dei lavoratori è aumentato di 10 volte, quindi la velocità di elaborazione doveva essere maggiore.
Ma dopo aver visto la schermata di monitoraggio dopo l’implementazione, sono rimasto bloccato. Il server in realtà si è mosso più lentamente, l’utilizzo della CPU è aumentato vertiginosamente, ma il numero di richieste elaborate è effettivamente diminuito. Sembrava che gli operai stessero semplicemente spalando per aria senza fare alcun lavoro.
Perché mai il numero dei lavoratori è aumentato, ma la fabbrica è diventata più lenta? Cercando il motivo, mi sono imbattuto nel costo più costoso del sistema operativo, il “cambio di contesto”.

Recensione: discussioni, ricordi?
Per coloro che leggono questo articolo per la prima volta o per coloro che non hanno familiarità con il contenuto dell’articolo precedente (Parte 4: Processi e thread), riavvolgiamo il nastro per un momento.
Nella nostra visione del mondo da “centro logistico digitale”:
Spring Boot è fondamentalmente multi-thread. Ogni volta che arriva una richiesta, un lavoratore (thread) viene assegnato a svolgere il lavoro. Quindi ho semplicemente pensato: “Se ci sono più lavoratori, verranno elaborate più richieste contemporaneamente, giusto?”
Ma c’era qualcosa che ho trascurato. È il numero di core CPU, i lavoratori chiave nella nostra fabbrica.
Lavoro a turni in un centro di distribuzione digitale
In effetti, il nucleo della CPU, il nucleo centrale di un computer, può eseguire solo un’attività alla volta. (Basato su single core) Tuttavia, ascoltiamo canzoni, codifichiamo e utilizziamo KakaoTalk allo stesso tempo. Com’è possibile?
Questo perché il direttore della fabbrica (OS) ordina ai lavoratori (CPU) di “spostare il lavoro” a una velocità incredibilmente elevata. “Riproduci la canzone per 0,001 secondi e fermati! Invia KakaoTalk per i prossimi 0,001 secondi e fermati!”
Si tratta di “Time Sharing” e il processo in cui il lavoratore mette giù lo strumento e ne prende uno nuovo è “Cambio di contesto”.
Il prezzo del cambio di attività: tempo per cambiarsi
Questo è quello che è successo quando ho aumentato il numero di thread a 2.000.
La CPU è un corpo unico, con 2.000 threadworker che gridano: “Liberatevene!” La CPU incontra continuamente 2.000 persone a turno per elaborare il lavoro in modo equo.
Il problema è che è necessario del “tempo di preparazione” quando si passa dal lavoro del lavoratore A al lavoro del lavoratore B.
Questo “tempo necessario per registrare, riporre e leggere” è chiamato “costo di cambio di contesto (spese generali)”. Quando ci sono lavoratori adeguati, questo costo è trascurabile. Ma cosa succede se i lavoratori sono troppi? La CPU si trova in una situazione in cui organizza i registri dei lavoratori tutto il giorno, ma non è in grado di svolgere alcun “lavoro effettivo (calcolo)”. Questo era il vero motivo per cui il mio server era lento.

[Verifica del codice] È necessariamente più veloce solo perché ci sono molti thread?
Vale la pena ascoltare. Dimostriamolo con il codice. Confrontiamo la velocità di esecuzione della stessa quantità di operazioni di addizione utilizzando un thread e dividendolo in 1 milione di thread. Il buon senso suggerisce che 1 milione di unità dovrebbe essere più veloce, ma la realtà è diversa.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ContextSwitchingTest {
private static final int TASK_COUNT = 1_000_000;
public static void main(String[] args) throws InterruptedException {
// 1. Elaborazione con thread singolo (senza turnazione)
long start = System.currentTimeMillis();
for (int i = 0; i < TASK_COUNT; i++) {
simpleTask();
}
System.out.println("Tempo thread singolo: " + (System.currentTimeMillis() - start) + "ms");
// 2. Elaborazione con moltissimi thread (provoca context switching)
// Crea un pool di thread illimitato (attenzione: il computer potrebbe bloccarsi)
ExecutorService executor = Executors.newCachedThreadPool();
start = System.currentTimeMillis();
for (int i = 0; i < TASK_COUNT; i++) {
executor.submit(() -> simpleTask());
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
System.out.println("Tempo multi-thread: " + (System.currentTimeMillis() - start) + "ms");
}
private static void simpleTask() {
int a = 1 + 1; // Operazione molto leggera
}
}
Risultati di esempio (varia in base all’ambiente):
Analisi: l’attività in sé (1+1) è così semplice che può essere completata in un batter d’occhio. Tuttavia, nel metodo multi-thread, il costo per creare un milione di thread e far passare avanti e indietro il sistema operativo da uno all’altro è migliaia di volte più costoso del tempo operativo. L’ombelico è più grande dello stomaco
Lezioni dalla pratica: trovare il punto giusto
Quindi quanti thread sono appropriati per un server Spring Boot? La risposta dipende da “cosa fa il server”.
Tuttavia, come nella situazione che ho vissuto, aumentare ciecamente il numero a 2.000 è troppo. Questo perché all’aumentare del numero di thread viene consumata più memoria (stack) e la CPU è sovraccarica a causa dei costi di cambio di contesto.
Recentemente, le tecnologie non bloccanti come Node.js e WebFlux di Spring stanno attirando l’attenzione per risolvere questo problema. Usano la strategia di “non aumentare il numero di thread, lascia che una persona elabori rapidamente senza fermarsi.”

Chiusura: non è previsto il pranzo gratuito
Spesso crediamo erroneamente che “elaborare più cose contemporaneamente sia più veloce”. Tuttavia, nel mondo dei computer, il “simultaneo” è in realtà solo lavoro a turni ad alta velocità, il che è quasi un trucco.
Una volta compreso il cambio di contesto, capirai perché l’ottimizzazione del server non significa solo “aumentare i numeri”. L’aumento indiscriminato dei thread può effettivamente diventare un veleno che soffoca il server.
Ora, i componenti interni del computer (CPU, RAM, processo) sembrano funzionare abbastanza bene. Ora apriamo la porta della fabbrica e usciamo. Come inviamo i dati creati nella nostra fabbrica ad un’altra fabbrica (cliente) lontana?
La prossima volta parleremo di reti, HTTP e di quella rete stradale invisibile.
555
555
555