Ingresso multithread Python con Esempio: Impara GIL in Python

โšก Riepilogo intelligente

Ingresso multithread Python Questo sistema esegue piรน thread all'interno di un singolo processo, consentendo loro di condividere la memoria e lavorare in parallelo. Il modulo di threading crea e gestisce questi thread, mentre il Global Interpreter Lock limita il parallelismo effettivo, rendendo questa tecnica piรน adatta ad attivitร  vincolate a input/output.

  • ๐Ÿ”˜ Thread vs processo: Un thread รจ un'unitร  di esecuzione all'interno di un processo e i thread condividono i dati del processo pur essendo eseguiti individualmente.
  • โ˜‘๏ธ Due moduli: Il modulo obsoleto _thread offre chiamate di basso livello, mentre il modulo threading di livello superiore รจ lo standard moderno per la creazione di thread.
  • โœ… Crea discussioni: Estendi la classe Thread, sovrascrivi __init__ e run(), quindi chiama start() per avviare e join() per attendere il completamento.
  • ๐Ÿงช Evitare le condizioni di gara: Un blocco consente a un solo thread di accedere a una sezione critica alla volta, prevenendo deadlock e danneggiamenti dei dati condivisi.
  • ๏ธ Comprendere il GIL: Il blocco dell'interprete globale consente l'esecuzione di un thread Python bytecode alla volta, quindi le operazioni che dipendono dalla CPU richiedono il multiprocessing.
  • ๐Ÿค– Carichi di lavoro dell'IA: Il multithreading velocizza le fasi di IA legate all'input/output come il caricamento dei dati, mentre NumPy e PyTorch rilascia il GIL per il calcolo parallelo.

Ingresso multithread Python

Migliori Python Il linguaggio di programmazione ti consente di utilizzare il multiprocessing o il multithreading. In questo tutorial imparerai come scrivere applicazioni multithread in Python.

Che cos'รจ un filo?

Un thread รจ un'unitร  di esecuzione nella programmazione concorrente. Il multithreading รจ una tecnica che consente a una CPU di eseguire simultaneamente molte attivitร  di un singolo processo. Questi thread possono essere eseguiti individualmente, pur condividendo le proprie risorse di processo.

Cos'รจ un processo?

Un processo รจ fondamentalmente il programma in esecuzione. Quando avvii un'applicazione sul tuo computer (come un browser o un editor di testo), il sistema operativo crea un processo.

In cosa consiste il multithreading Python?

Ingresso multithread Python La programmazione multithreading รจ una tecnica ben nota in cui piรน thread all'interno di un processo condividono il proprio spazio dati con il thread principale, rendendo la condivisione delle informazioni e la comunicazione tra i thread facili ed efficienti. I thread sono piรน leggeri dei processi. Piรน thread possono essere eseguiti individualmente condividendo le risorse del processo. Lo scopo del multithreading รจ quello di eseguire piรน attivitร  e funzioni contemporaneamente.

Cos'รจ la multielaborazione?

multiprocessing consente di eseguire piรน processi non correlati contemporaneamente. Questi processi non condividono le loro risorse e comunicano tramite IPC.

Python Multithreading e multiprocessing

Per comprendere processi e thread, consideriamo questo scenario: un file .exe sul computer รจ un programma. Quando lo apri, il sistema operativo lo carica in memoria e la CPU lo esegue. L'istanza del programma in esecuzione viene chiamata processo.

Ogni processo ha due componenti fondamentali:

  • Migliori Code
  • I dati

Ora, un processo puรฒ contenere una o piรน sottoparti chiamate thread. Dipende dall'architettura del sistema operativo. Si puรฒ pensare a un thread come a una sezione di un processo che puรฒ essere eseguita separatamente dal sistema operativo.

In altre parole, si tratta di un flusso di istruzioni che possono essere eseguite in modo indipendente dal sistema operativo. I thread all'interno di un singolo processo condividono i dati di quel processo e sono progettati per lavorare insieme al fine di facilitare il parallelismo.

Perchรฉ utilizzare il multithreading?

Il multithreading consente di suddividere un'applicazione in piรน sotto-attivitร  ed eseguirle simultaneamente. Se si utilizza il multithreading correttamente, la velocitร , le prestazioni e il rendering dell'applicazione possono essere tutti migliorati.

Python multithreading

Python supporta costrutti sia per il multiprocessing che per il multithreading. In questo tutorial, ti concentrerai principalmente sull'implementazione multithreaded applicazioni con Python. Esistono due moduli principali che possono essere utilizzati per gestire i thread in Python:

  1. Migliori filo modulo, e
  2. Migliori threading modulo

Tuttavia, in Python, esiste anche qualcosa chiamato blocco globale dell'interprete (GIL). Non consente un grande guadagno in termini di prestazioni e potrebbe persino ridurre le prestazioni di alcune applicazioni multithread. Imparerai tutto al riguardo nelle prossime sezioni di questo tutorial.

I moduli Thread e Threading

I due moduli che imparerai in questo tutorial sono il modulo filo e modulo di filettatura.

Tuttavia, il modulo thread รจ stato da tempo deprecato. A partire da Python 3, รจ stato designato come obsoleto ed รจ accessibile solo come _filo per la retrocompatibilitร .

Dovresti usare il livello piรน alto threading modulo per le applicazioni che si intende distribuire. Il modulo thread รจ stato trattato qui solo a scopo didattico.

Il modulo Discussione

La sintassi per creare un nuovo thread utilizzando questo modulo รจ la seguente:

thread.start_new_thread(function_name, arguments)

Bene, ora hai trattato la teoria di base per iniziare a programmare. Quindi, apri il tuo IDLE oppure un blocco note e digitare quanto segue:

import time
import _thread

def thread_test(name, wait):
   i = 0
   while i <= 3:
      time.sleep(wait)
      print("Running %s\n" %name)
      i = i + 1

   print("%s has finished execution" %name)

if __name__ == "__main__":
    
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

Salva il file e premi F5 per eseguire il programma. Se tutto รจ stato fatto correttamente, questo รจ l'output che dovresti vedere:

Il modulo Discussione

Nelle sezioni successive troverai maggiori informazioni sulle condizioni di gara e su come gestirle.

Il modulo Discussione

SPIEGAZIONE DEL CODICE

  1. Queste istruzioni importano il modulo time e thread, che vengono utilizzati per gestire l'esecuzione e il ritardo del Python thread.
  2. Qui hai definito una funzione chiamata test_thread, che sarร  chiamato dal start_new_thread Metodo. La funzione esegue un ciclo while per quattro iterazioni e stampa il nome del thread che l'ha chiamata. Una volta completata l'iterazione, stampa un messaggio che indica che il thread ha terminato l'esecuzione.
  3. Questa รจ la sezione principale del tuo programma. Qui, chiami semplicemente il start_new_thread metodo con il thread_test funzione come argomento. Questo creerร  un nuovo thread per la funzione che passi come argomento e inizierร  a eseguirla. Nota che puoi sostituire questo (thread_test) con qualsiasi altra funzione che desideri eseguire come thread.

Il modulo di threading

Questo modulo รจ l'implementazione di alto livello del threading in Python e rappresenta lo standard di fatto per la gestione di applicazioni multithread. Offre una vasta gamma di funzionalitร  rispetto al modulo thread.

Struttura del modulo Threading

Struttura del modulo Threading

Ecco un elenco di alcune funzioni utili definite in questo modulo:

Nome della funzione Descrizione
conteggioattivo() Restituisce il conteggio di Filo oggetti che sono ancora vivi.
thread corrente() Restituisce l'oggetto corrente della classe Thread.
enumerare() Elenca tutti gli oggetti Thread attivi.
รจDaemon() Restituisce vero se il thread รจ un demone.
รจ vivo() Restituisce vero se il thread รจ ancora vivo.
Metodi della classe thread
inizio() Avvia l'attivitร  di un thread. Deve essere chiamato solo una volta per ciascun thread perchรฉ genererร  un errore di runtime se chiamato piรน volte.
correre() Questo metodo denota l'attivitร  di un thread e puรฒ essere sovrascritto da una classe che estende la classe Thread.
aderire() Blocca l'esecuzione di altro codice finchรฉ il thread su cui รจ stato chiamato il metodo join() non viene terminato.

Storia: la classe Thread

Prima di iniziare a programmare con il modulo threading, รจ fondamentale comprendere la classe Thread. La classe thread รจ la classe principale che definisce il modello e le operazioni di un thread in Python.

Il modo piรน comune per creare un multithread Python L'applicazione consiste nel dichiarare una classe che estende la classe Thread e sovrascrive il suo metodo run().

La classe Thread, in sintesi, indica una sequenza di codice che viene eseguita in un file separato filo di controllo.

Quindi, quando scrivi un'app multithread, dovrai fare quanto segue:

  1. definire una classe che estende la classe Thread
  2. Sostituisci il __init__ costruttore
  3. Sostituisci il correre() metodo

Una volta creato un oggetto thread, il file inizio() il metodo puรฒ essere utilizzato per avviare l'esecuzione di questa attivitร  e il aderire() Il metodo puรฒ essere utilizzato per bloccare tutto il resto del codice fino al termine dell'attivitร  corrente.

Ora proviamo a usare il modulo threading per implementare il tuo esempio precedente. Di nuovo, avvia il tuo IDLE e digita quanto segue:

import time
import threading

class threadtester (threading.Thread):
    def __init__(self, id, name, i):
       threading.Thread.__init__(self)
       self.id = id
       self.name = name
       self.i = i
       
    def run(self):
       thread_test(self.name, self.i, 5)
       print ("%s has finished execution " %self.name)

def thread_test(name, wait, i):

    while i:
       time.sleep(wait)
       print ("Running %s \n" %name)
       i = i - 1

if __name__=="__main__":
    thread1 = threadtester(1, "First Thread", 1)
    thread2 = threadtester(2, "Second Thread", 2)
    thread3 = threadtester(3, "Third Thread", 3)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()

Questo sarร  l'output quando esegui il codice sopra:

Storia: la classe Thread

SPIEGAZIONE DEL CODICE

Storia: la classe Thread

  1. Questa parte รจ uguale al nostro esempio precedente. Qui si importano i moduli time e thread, che vengono utilizzati per gestire l'esecuzione e i ritardi del Python thread.
  2. In questo momento stai creando una classe chiamata threadtester, che eredita o estende il file Filo classe del modulo threading. Questo รจ uno dei modi piรน comuni per creare thread in PythonTuttavia, dovresti sovrascrivere solo il costruttore e il correre() metodo nella tua app. Come puoi vedere nell'esempio di codice sopra, il file __init__ il metodo (costruttore) รจ stato sovrascritto. Allo stesso modo, hai anche sovrascritto il file correre() metodo. Contiene il codice che vuoi eseguire all'interno di un thread. In questo esempio, hai chiamato la funzione thread_test().
  3. Questo รจ il metodo thread_test(), che prende il valore di i come argomento, lo decrementa di 1 ad ogni iterazione e ripete il resto del codice finchรฉ i non diventa 0. Ad ogni iterazione, stampa il nome del thread attualmente in esecuzione e si mette in pausa per wait secondi (che viene anch'esso preso come argomento).
  4. thread1 = threadtester(1, โ€œFirst Threadโ€, 1) Qui stiamo creando un thread e passando i tre parametri che abbiamo dichiarato in __init__. Il primo parametro รจ l'id del thread, il secondo parametro รจ il nome del thread e il terzo parametro รจ il contatore, che determina quante volte deve essere eseguito il ciclo while.
  5. thread2.start() Il metodo start viene utilizzato per avviare l'esecuzione di un thread. Internamente, la funzione start() chiama il metodo run() della tua classe.
  6. thread3.join() Il metodo join() blocca l'esecuzione di altro codice e attende fino al termine del thread su cui รจ stato chiamato.

Come giร  sapete, i thread che si trovano nello stesso processo hanno accesso alla memoria e ai dati di quel processo. Di conseguenza, se piรน di un thread tenta di modificare o accedere ai dati contemporaneamente, potrebbero verificarsi degli errori.

Nella sezione successiva, verranno illustrate le diverse tipologie di complicazioni che possono insorgere quando i thread accedono ai dati e la sezione critica relativa alla verifica delle transazioni di accesso esistenti.

Stalli e condizioni di gara

Prima di approfondire i concetti di deadlock e race condition, รจ utile comprendere alcune definizioni di base relative alla programmazione concorrente:

  • Sezione critica: Si tratta di un frammento di codice che accede o modifica variabili condivise e deve essere eseguito come transazione atomica.
  • Cambio di contesto: Si tratta del processo che una CPU segue per memorizzare lo stato di un thread prima di passare da un'attivitร  all'altra, in modo da poterlo riprendere successivamente dallo stesso punto.

Deadlock

Deadlock sono il problema piรน temuto che gli sviluppatori affrontano quando scrivono applicazioni concorrenti/multithread in PythonIl modo migliore per comprendere i deadlock รจ utilizzare il classico problema di esempio dell'informatica noto come Ristoranti PhiloProblema di Sopher.

Il problema per i filosofi della tavola รจ il seguente:

Cinque filosofi sono seduti attorno a un tavolo rotondo con cinque piatti di spaghetti (un tipo di pasta) e cinque forchette, come mostrato nel diagramma.

Ristoranti PhiloProblema di Sopher

Ristoranti PhiloProblema di Sopher

In ogni momento, un filosofo deve stare mangiando o pensando.

Inoltre, un filosofo deve prendere le due forchette adiacenti a lui (cioรจ, la forchetta sinistra e quella destra) prima di poter mangiare gli spaghetti. Il problema dello stallo si verifica quando tutti e cinque i filosofi prendono contemporaneamente la loro forchetta destra.

Poichรฉ ognuno dei filosofi ha una forchetta, aspetteranno tutti che gli altri la appoggino. Di conseguenza, nessuno di loro potrร  mangiare gli spaghetti.

Allo stesso modo, in un sistema concorrente, si verifica un deadlock quando diversi thread o processi (filosofi) tentano di acquisire le risorse di sistema condivise (fork) contemporaneamente. Di conseguenza, nessuno dei processi ha la possibilitร  di essere eseguito poichรฉ sono in attesa di un'altra risorsa detenuta da un altro processo.

Condizioni di regata

Una condizione di gara (race condition) รจ uno stato indesiderato di un programma che si verifica quando un sistema esegue due o piรน operazioni simultaneamente. Ad esempio, si consideri questo semplice ciclo for:

i=0; # a global variable
for x in range(100):
    print(i)
    i+=1;

Se crei n Poichรฉ il numero di thread che eseguono questo codice contemporaneamente non consente di determinare il valore di i (che รจ condiviso dai thread) al termine dell'esecuzione del programma, in un ambiente multithreading reale i thread possono sovrapporsi e il valore di i, recuperato e modificato da un thread, puรฒ cambiare nel frattempo quando un altro thread vi accede.

Queste sono le due principali classi di problemi che possono verificarsi in un sistema multithread o distribuito Python applicazione. Nella prossima sezione, imparerai come superare questo problema sincronizzando i thread.

Syncfili cronici

Per gestire condizioni di gara, deadlock e altri problemi basati sui thread, il modulo di threading fornisce bloccare oggetto. L'idea รจ che quando un thread vuole accedere a una risorsa specifica, acquisisce un blocco per quella risorsa. Una volta che un thread blocca una risorsa particolare, nessun altro thread puรฒ accedervi finchรฉ il blocco non viene rilasciato. Di conseguenza, le modifiche alla risorsa saranno atomiche e le condizioni di gara saranno evitate.

Un blocco รจ una primitiva di sincronizzazione di basso livello implementata da _filo modulo. In un dato momento, una serratura puรฒ trovarsi in uno dei due stati: bloccato or sbloccato. Supporta due metodi:

  1. acquisire(): Quando lo stato di blocco viene sbloccato, la chiamata al metodo acquire() cambierร  lo stato in bloccato e terminerร . Tuttavia, se lo stato รจ bloccato, la chiamata a acquire() viene bloccata finchรฉ il metodo release() non viene chiamato da un altro thread.
  2. pubblicazione(): Il metodo release() viene utilizzato per impostare lo stato su sbloccato, ovvero per rilasciare un blocco. Puรฒ essere chiamato da qualsiasi thread, non necessariamente da quello che ha acquisito il lock.

Ecco un esempio di come usare i lucchetti nelle tue app. Avvia la tua IDLE e digita quanto segue:

import threading
lock = threading.Lock()

def first_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the first funcion')
        lock.release()

def second_function():
    for i in range(5):
        lock.acquire()
        print ('lock acquired')
        print ('Executing the second funcion')
        lock.release()

if __name__=="__main__":
    thread_one = threading.Thread(target=first_function)
    thread_two = threading.Thread(target=second_function)

    thread_one.start()
    thread_two.start()

    thread_one.join()
    thread_two.join()

Ora premi F5. Dovresti vedere un output come questo:

SyncDiscussioni croniche

SPIEGAZIONE DEL CODICE

SyncDiscussioni croniche

  1. In questo caso stai semplicemente creando una nuova serratura chiamando il file threading.Lock() funzione di fabbrica. Internamente, Lock() restituisce un'istanza della classe Lock concreta piรน efficace gestita dalla piattaforma.
  2. Nella prima istruzione acquisisci il blocco chiamando il metodo acquire(). Una volta concesso il blocco, si stampa โ€œblocco acquisitoโ€ alla consolle. Una volta terminata l'esecuzione di tutto il codice che desideri venga eseguito dal thread, rilasci il blocco chiamando il metodo release().

La teoria รจ valida, ma come si fa a sapere se il blocco ha funzionato davvero? Se si osserva l'output, si noterร  che ogni istruzione print stampa esattamente una riga alla volta. Ricordiamo che, in un esempio precedente, gli output di print erano casuali perchรฉ piรน thread accedevano contemporaneamente al metodo print(). Qui, la funzione print viene chiamata solo dopo che il blocco รจ stato acquisito. Pertanto, gli output vengono visualizzati uno alla volta e riga per riga.

Oltre alle serrature, Python Supporta inoltre altri meccanismi per la gestione della sincronizzazione dei thread, come elencato di seguito:

  1. RLocks
  2. Semaphores
  3. Condizioni
  4. Eventi, e
  5. Barriere

Blocco globale interprete (e come gestirlo)

Prima di entrare nei dettagli di PythonPrima di iniziare, definiamo alcuni termini che saranno utili per comprendere la sezione successiva:

  1. Codice legato alla CPU: si riferisce a qualsiasi porzione di codice che verrร  eseguita direttamente dalla CPU.
  2. Codice vincolato all'I/O: si tratta di qualsiasi codice che accede al file system tramite il sistema operativo.
  3. CPython: รจ il riferimento implementazione of Python e puรฒ essere descritto come l'interprete scritto in C e Python (linguaggio di programmazione).

In cosa consiste GIL Python?

Blocco globale dell'interprete (GIL) in Python รจ un blocco di processo o un mutex utilizzato durante la gestione dei processi. Assicura che un solo thread possa accedere a una risorsa specifica alla volta e impedisce anche l'utilizzo simultaneo di oggetti e bytecode. Ciรฒ avvantaggia i programmi a singolo thread con un aumento delle prestazioni. GIL in Python รจ molto semplice e facile da implementare.

รˆ possibile utilizzare un blocco per assicurarsi che solo un thread abbia accesso a una particolare risorsa in un dato momento.

Una delle caratteristiche di Python รจ che utilizza un blocco globale su ogni processo interprete, il che significa che ogni processo tratta il Python l'interprete stesso come risorsa.

Ad esempio, supponiamo che tu abbia scritto un Python Programma che utilizza due thread per eseguire sia operazioni di CPU che operazioni di I/O. Quando si esegue questo programma, accade quanto segue:

  1. Migliori Python L'interprete crea un nuovo processo e avvia i thread.
  2. Quando il thread-1 inizia a funzionare, acquisirร  prima il GIL e lo bloccherร .
  3. Se il thread-2 vuole essere eseguito adesso, dovrร  attendere il rilascio del GIL anche se un altro processore รจ libero.
  4. Supponiamo ora che il thread-1 sia in attesa di un'operazione di I/O. A questo punto, rilascerร  il GIL e il thread-2 lo acquisirร .
  5. Dopo aver completato le operazioni di I/O, se il thread-1 vuole essere eseguito adesso, dovrร  nuovamente attendere che il GIL venga rilasciato dal thread-2.

Per questo motivo, solo un thread puรฒ accedere all'interprete in qualsiasi momento, il che significa che ci sarร  un solo thread in esecuzione Python codice in un dato momento.

Questo non rappresenta un problema per un processore single-core, poichรฉ utilizzerebbe la tecnica del time slicing (vedi la prima sezione di questo tutorial) per gestire i thread. Tuttavia, nel caso di processori multi-core, una funzione CPU-bound in esecuzione su piรน thread avrร  un impatto considerevole sull'efficienza del programma, poichรฉ non utilizzerร  tutti i core disponibili contemporaneamente.

Perchรฉ era necessario il GIL?

Il CPython Il garbage collector utilizza una tecnica efficiente di gestione della memoria nota come conteggio dei riferimenti. Ecco come funziona: ogni oggetto in Python Gli oggetti hanno un contatore di riferimenti, che viene incrementato quando vengono assegnati a una nuova variabile o aggiunti a un contenitore (come tuple, liste, ecc.). Allo stesso modo, il contatore di riferimenti viene decrementato quando il riferimento esce dall'ambito di validitร  o quando viene chiamata l'istruzione `del`. Quando il contatore di riferimenti di un oggetto raggiunge 0, questo viene eliminato dal garbage collector e la memoria allocata viene liberata.

Ma il problema รจ che la variabile di conteggio dei riferimenti รจ soggetta a condizioni di gara come qualsiasi altra variabile globale. Per risolvere questo problema, gli sviluppatori di Python Abbiamo deciso di utilizzare il blocco globale dell'interprete. L'altra opzione era aggiungere un blocco a ciascun oggetto, il che avrebbe comportato deadlock e un aumento del sovraccarico dovuto alle chiamate acquire() e release().

Pertanto, GIL rappresenta una restrizione significativa per il multithreading Python programmi che eseguono operazioni pesanti legate alla CPU (rendendole di fatto a singolo thread). Se si desidera utilizzare piรน core della CPU nell'applicazione, utilizzare il multiprocessing modulo invece.

DOMANDE FREQUENTI

Utilizza il threading per le operazioni di I/O intensive, come le chiamate di rete, l'accesso ai file o le query di database, in cui i thread attendono risorse esterne. Utilizza il multiprocessing per le operazioni che dipendono dalla CPU, come i calcoli complessi, poichรฉ viene eseguito su piรน core e bypassa il GIL (Global Interpreter Lock).

Non di default. PEP 703 ha introdotto una build opzionale con thread liberi in Python 3.13 che disabilita il GIL e Python La versione 3.14 continua su questa linea. Le build standard includono ancora il GIL, quindi la maggior parte dei programmi continua a funzionare esattamente come prima.

Un thread daemon viene eseguito in background e non impedisce al programma di terminare. Quando rimangono solo thread daemon, Python li arresta e termina. Impostane uno con thread.daemon = True prima di chiamare start().

La classe Thread non restituisce direttamente il risultato di run(). Memorizza il valore nell'oggetto thread o in una coda condivisa, oppure utilizza concurrent.futures.ThreadPoolExecutor, il cui metodo submit() restituisce un Future che puoi leggere con result().

ThreadPoolExecutor รจ una funzione di supporto di alto livello che gestisce un pool di thread di lavoro. รˆ possibile inviare attivitร  tramite i metodi submit() o map(), e ThreadPoolExecutor si occupa della creazione, del riutilizzo e della pulizia dei thread, restituendo oggetti Future che contengono ciascun risultato.

รˆ possibile creare centinaia o migliaia di thread, ma il GIL consente l'esecuzione di un solo thread alla volta. Python bytecode in qualsiasi istante. Si sovrappongono ancora durante le attese di I/O, quindi un elevato numero di thread concorrenti รจ di grande aiuto per i programmi con elevato I/O rispetto a quelli con elevato CPU.

Sรฌ, principalmente per le fasi I/O-bound come il caricamento dei dati, la chiamata di API o la lettura di file. L'addestramento intensivo รจ CPU-bound, quindi librerie come NumPy e PyTorrilasciare il GIL nel codice C o affidarsi al multiprocessing per un vero parallelismo.

Sรฌ. GitHub Copilot completa automaticamente modelli comuni come sottoclassi di Thread, utilizzo di Lock e configurazioni di ThreadPoolExecutor a partire da un commento o dal nome di una funzione. Prima di utilizzare il codice generato, รจ sempre consigliabile verificarne la presenza di race condition e la corretta gestione dei lock.

Riassumi questo post con: