Condividi tramite


Race conditions and deadlocks

Visual Basic .NET o Visual Basic offre la possibilità di usare thread nelle applicazioni Visual Basic per la prima volta. I thread introducono problemi di debug, ad esempio race condition e deadlock. Questo articolo illustra questi due problemi.

Versione originale del prodotto: Visual Basic, Visual Basic .NET
Numero KB originale: 317723

Quando si verificano le condizioni di gara

Una race condition si verifica quando due thread accedono a una variabile condivisa contemporaneamente. Il primo thread legge la variabile e il secondo thread legge lo stesso valore dalla variabile. Il primo thread e il secondo thread eseguono quindi le operazioni sul valore e mettono in gara per vedere quale thread può scrivere il valore per ultima nella variabile condivisa. Il valore del thread che scrive l'ultimo valore viene mantenuto, perché il thread sta scrivendo sul valore scritto dal thread precedente.

Dettagli ed esempi per una race condition

Ogni thread viene allocato un periodo di tempo predefinito da eseguire in un processore. Quando scade il tempo allocato per il thread, il contesto del thread viene salvato fino al successivo attivazione del processore e il processore inizia l'esecuzione del thread successivo.

In che modo un comando da una riga può causare una race condition

Esaminare l'esempio seguente per vedere come si verifica una race condition. Sono presenti due thread ed entrambi aggiornano una variabile condivisa denominata total (rappresentata come dword ptr ds:[031B49DCh] nel codice dell'assembly).

  • Thread 1

    Total = Total + val1
    
  • Thread 2

    Total = Total - val2
    

Codice assembly (con numeri di riga) dalla compilazione del codice Visual Basic precedente:

  • Thread 1

    1. mov eax,dword ptr ds:[031B49DCh]
    2. add eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 7611097F
    6. mov dword ptr ds:[031B49DCh],eax
    
  • Thread 2

    1. mov eax,dword ptr ds:[031B49DCh]
    2. sub eax,edi
    3. jno 00000033
    4. xor ecx,ecx
    5. call 76110BE7
    6. mov dword ptr ds:[031B49DCh],eax
    

Esaminando il codice dell'assembly, è possibile visualizzare il numero di operazioni eseguite dal processore al livello inferiore per eseguire un semplice calcolo di addizione. Un thread può essere in grado di eseguire tutto o parte del codice assembly durante il relativo tempo nel processore. Esaminare ora come si verifica una race condition da questo codice.

Total è 100, val1 è 50 e val2 è 15. Il thread 1 consente di eseguire ma completa solo i passaggi da 1 a 3. Ciò significa che Thread 1 legge la variabile e completa l'aggiunta. Thread 1 è ora in attesa di scrivere il nuovo valore di 150. Dopo l'arresto del thread 1 , il thread 2 viene eseguito completamente. Ciò significa che ha scritto il valore calcolato (85) nella variabile Total. Infine, Thread 1 recupera il controllo e termina l'esecuzione. Scrive il valore (150). Pertanto, al termine del thread 1 , il valore di Total è ora 150 anziché 85.

È possibile vedere come potrebbe trattarsi di un problema importante. Se si tratta di un programma bancario, il cliente avrebbe denaro nel proprio conto che non dovrebbe essere presente.

Questo errore è casuale, perché è possibile che Thread 1 completi l'esecuzione prima della scadenza del processore e quindi Thread 2 possa iniziare l'esecuzione. Se si verificano questi eventi, il problema non si verifica. L'esecuzione del thread non è deterministica, pertanto non è possibile controllare l'ora o l'ordine di esecuzione. Si noti anche che i thread possono essere eseguiti in modo diverso in fase di esecuzione rispetto alla modalità di debug. Inoltre, è possibile notare che se si esegue ogni thread in serie, l'errore non si verifica. Questa casualità rende questi errori molto più difficili da rilevare ed eseguire il debug.

Per evitare che si verifichino race condition, è possibile bloccare le variabili condivise, in modo che un solo thread alla volta abbia accesso alla variabile condivisa. Eseguire questa operazione con moderazione, perché se una variabile è bloccata in Thread 1 e Thread 2 richiede anche la variabile, l'esecuzione del thread 2 si arresta mentre thread 2 attende che Thread 1 rilasci la variabile. Per altre informazioni, vedere SyncLock nella sezione Riferimenti di questo articolo.

Sintomi per una race condition

Il sintomo più comune di una race condition è valori imprevedibili di variabili condivise tra più thread. Ciò deriva dall'imprevedibilità dell'ordine in cui vengono eseguiti i thread. A volte un thread vince e a volte l'altro thread vince. In altri casi, l'esecuzione funziona correttamente. Inoltre, se ogni thread viene eseguito separatamente, il valore della variabile si comporta correttamente.

Quando si verificano deadlock

Un deadlock si verifica quando due thread bloccano contemporaneamente una variabile diversa e quindi tentano di bloccare la variabile già bloccata dall'altro thread. Di conseguenza, ogni thread smette di eseguire e attende che l'altro thread rilasci la variabile. Poiché ogni thread contiene la variabile desiderata dall'altro thread, non si verifica nulla e i thread rimangono deadlock.

Dettagli ed esempi per deadlock

Il codice seguente include due oggetti e LeftVal RightVal:

  • Thread 1

    SyncLock LeftVal
        SyncLock RightVal
            'Perform operations on LeftVal and RightVal that require read and write.
        End SyncLock
    End SyncLock
    
  • Thread 2

    SyncLock RightVal
        SyncLock LeftVal
            'Perform operations on RightVal and LeftVal that require read and write.
        End SyncLock
    End SyncLock
    

Un deadlock si verifica quando thread 1 è autorizzato a bloccare LeftVal. Il processore arresta l'esecuzione del thread 1 e inizia l'esecuzione del thread 2. Il thread 2 blocca RightVal e quindi tenta di bloccare LeftVal. Poiché LeftVal è bloccato, il thread 2 si arresta e attende il LeftVal rilascio. Poiché il thread 2 viene arrestato, thread 1 può continuare l'esecuzione. Il thread 1 tenta di bloccare RightVal ma non può, perché thread 2 lo ha bloccato. Di conseguenza, Thread 1 inizia ad attendere fino a quando RightVal non diventa disponibile. Ogni thread attende l'altro thread, perché ogni thread ha bloccato la variabile in attesa dell'altro thread e nessuno dei due thread sta sbloccando la variabile che contiene.

Un deadlock non si verifica sempre. Se Thread 1 esegue entrambi i blocchi prima che il processore lo arresti, Thread 1 può eseguire le operazioni e quindi sbloccare la variabile condivisa. Dopo che Thread 1 sblocca la variabile, Thread 2 può procedere con l'esecuzione, come previsto.

Questo errore sembra ovvio quando questi frammenti di codice vengono posizionati side-by-side, ma in pratica il codice può apparire in moduli o aree separate del codice. Si tratta di un errore difficile da rilevare perché, da questo stesso codice, può verificarsi sia l'esecuzione corretta che l'esecuzione non corretta.

Sintomi per i deadlock

Un sintomo comune del deadlock è che il programma o il gruppo di thread smette di rispondere. Questo è noto anche come blocco. Almeno due thread sono in attesa di una variabile bloccata dall'altro thread. I thread non procedono, perché nessuno dei thread rilascia la variabile fino a quando non ottiene l'altra variabile. L'intero programma può bloccarsi se il programma è in attesa di uno o entrambi i thread per completare l'esecuzione.

Che cos'è un thread

I processi vengono usati per separare le diverse applicazioni in esecuzione in un determinato momento in un singolo computer. Il sistema operativo non esegue processi, ma i thread lo fanno. Un thread è un'unità di esecuzione. Il sistema operativo alloca il tempo del processore a un thread per l'esecuzione delle attività del thread. Un singolo processo può contenere più thread di esecuzione. Ogni thread gestisce i propri gestori di eccezioni, le priorità di pianificazione e un set di strutture usate dal sistema operativo per salvare il contesto del thread se il thread non può completare l'esecuzione durante il periodo in cui è stato assegnato al processore. Il contesto viene mantenuto fino alla successiva ricezione del tempo del processore da parte del thread. Il contesto include tutte le informazioni necessarie al thread per continuare facilmente l'esecuzione. Queste informazioni includono il set di registri del processore del thread e lo stack di chiamate all'interno dello spazio indirizzi del processo host.

Riferimenti

Per altre informazioni, cercare nella Guida di Visual Studio le parole chiave seguenti:

  • SyncLock. Consente di bloccare un oggetto. Se un altro thread tenta di bloccare lo stesso oggetto, viene bloccato fino al rilascio del primo thread. Usare SyncLock attentamente, perché i problemi possono derivare dall'uso improprio di SyncLock. Ad esempio, questo comando può impedire le race condition ma causare deadlock.

  • InterLocked. Consente un set selezionato di operazioni thread-safe su variabili numeriche di base.

Per altre informazioni, vedere Thread e threading.