Qual è la contesa sui thread?

Qualcuno può spiegare semplicemente quale contesa sul thread è?

L’ho cercato su google, ma non riesco a trovare una spiegazione semplice.

Essenzialmente la contesa del thread è una condizione in cui un thread è in attesa di un blocco / object che è attualmente trattenuto da un altro thread. Pertanto, questo thread in attesa non può utilizzare quell’object finché l’altro thread non ha sbloccato quell’object particolare.

Diverse risposte sembrano concentrarsi sulla contesa del blocco, ma i blocchi non sono le sole risorse su cui può essere vissuta la contesa. La contesa è semplicemente quando due thread tentano di accedere alla stessa risorsa o alle risorse correlate in modo tale che almeno uno dei thread contendenti venga eseguito più lentamente di quanto sarebbe se gli altri thread non fossero in esecuzione.

L’esempio più ovvio di contesa è su un lucchetto. Se il thread A ha un lock e il thread B vuole acquisire lo stesso lock, il thread B dovrà attendere che il thread A rilasci il lock.

Ora, questo è specifico per la piattaforma, ma il thread può subire rallentamenti anche se non deve mai attendere che l’altro thread rilasci il blocco! Questo perché un lucchetto protegge alcuni tipi di dati, e spesso i dati stessi saranno contesi.

Ad esempio, considera un thread che acquisisce un blocco, modifica un object, quindi rilascia il blocco e fa alcune altre cose. Se due thread stanno facendo questo, anche se non combattono mai per il lock, i thread possono essere eseguiti molto più lentamente di quanto farebbero se fosse in esecuzione un solo thread.

Perché? Supponiamo che ogni thread funzioni sul proprio core su una moderna CPU x86 e che i core non condividano una cache L2. Con un solo thread, l’object può rimanere nella cache L2 per la maggior parte del tempo. Con entrambi i thread in esecuzione, ogni volta che un thread modifica l’object, l’altro thread troverà i dati non nella sua cache L2 perché l’altra CPU ha invalidato la riga della cache. Su un Pentium D, ad esempio, ciò farà sì che il codice funzioni alla velocità dell’FSB, che è molto inferiore alla velocità della cache L2.

Poiché la contesa può verificarsi anche se il blocco non viene conteso, la contesa può verificarsi anche in assenza di blocco. Ad esempio, supponiamo che la tua CPU supporti un incremento atomico di una variabile a 32 bit. Se un thread continua ad incrementare e decrementare una variabile, la variabile sarà molto calda nella cache per la maggior parte del tempo. Se due thread lo fanno, le loro cache contenderanno la proprietà della memoria che contiene quella variabile, e molti accessi saranno più lenti man mano che il protocollo di coerenza della cache opera per proteggere ogni proprietà principale della linea della cache.

Ironia della sorte, le serrature in genere riducono la contesa. Perché? Perché senza un blocco, due thread potrebbero operare sullo stesso object o raccolta e causare molti conflitti (ad esempio, esistono code senza blocco). I blocchi tenderanno a deschedule i thread in conflitto, consentendo invece l’esecuzione di thread non contendenti. Se il thread A contiene un lock e il thread B vuole quello stesso lock, l’implementazione può eseguire il thread C. Se il thread C non ha bisogno di quel blocco, la contesa futura tra i thread A e B può essere evitata per un po ‘. (Naturalmente, questo presuppone che ci siano altri thread che potrebbero essere eseguiti. Non sarà di aiuto se l’unico modo in cui il sistema nel suo complesso può fare progressi utili è l’esecuzione di thread che contendono.)

Da qui :

Una contesa si verifica quando un thread è in attesa di una risorsa che non è prontamente disponibile; rallenta l’esecuzione del codice, ma può risolversi nel tempo.

Un deadlock si verifica quando un thread è in attesa di una risorsa bloccata da un secondo thread e il secondo thread è in attesa di una risorsa bloccata dal primo thread. Più di due thread possono essere coinvolti in un deadlock. Un deadlock non si risolve mai da solo. Spesso provoca l’arresto dell’intera applicazione o della parte in cui si verifica il deadlock.

Hai 2 discussioni. Thread A e Thread B, hai anche l’object C.

A sta attualmente accedendo all’object C e ha posizionato un blocco su quell’object. B deve accedere all’object C, ma non può farlo finché A non rilascia il blocco sull’object C.

Penso che ci dovrebbe essere qualche chiarimento dal PO sullo sfondo della domanda – Posso pensare a 2 risposte (anche se sono sicuro che ci sono aggiunte a questa lista):

  1. se ti riferisci al “concetto” generale di contesa sul thread e come può presentarsi in un’applicazione, rimando alla risposta dettagliata di @ DavidSchwartz sopra.

  2. Esiste anche il contatore delle prestazioni “Blocchi e thread CLR .NET: Total # of Contentions”. Come preso dalla descrizione PerfMon per questo contatore, è definito come:

    Questo contatore visualizza il numero totale di volte in cui i thread nel CLR hanno tentato di acquisire un blocco gestito senza successo. Le serrature gestite possono essere acquisite in molti modi; con l’istruzione “lock” in C # o chiamando System.Monitor.Enter o usando l’attributo personalizzato MethodImplOptions.Synchronized.

… e sono sicuro che altri per altri OS e framework applicativi.

Un’altra parola potrebbe essere la concorrenza. È semplicemente l’idea di due o più thread che cercano di utilizzare la stessa risorsa.

Per me la contesa è una competizione tra 2 o più thread su una risorsa condivisa. La risorsa può essere un lucchetto, un contatore, ecc. Concorrenza significa “chi ottiene per primo”. Più thread più contesa. Più frequente è l’accesso a una risorsa, maggiore è la contesa.

La contesa del thread è influenzata anche dalle operazioni di I / O. Esempio quando un thread in attesa di lettura del file può essere considerato come una contesa. Utilizzare le porte di completamento I / O come soluzione.

La contesa del blocco si verifica quando un thread tenta di acquisire il blocco su un object che è già acquisito da un altro thread *. Finché l’object non viene rilasciato, il thread è bloccato (in altre parole, è in stato di attesa). In alcuni casi, ciò potrebbe portare a una cosiddetta esecuzione seriale che influisce negativamente sull’applicazione.

dalla documentazione dotTrace

Immagina il seguente scenario. Ti stai preparando per l’esame finale di domani e ti senti un po ‘affamato. Quindi dai dieci dollari a tuo fratello minore e chiedigli di comprare una pizza per te. In questo caso, tu sei il thread principale e tuo fratello è un thread figlio. Una volta dato l’ordine, sia tu che tuo fratello fate il loro lavoro contemporaneamente (ad esempio, studiando e acquistando una pizza). Ora, abbiamo due casi da considerare. In primo luogo, tuo fratello ti riporta la pizza e termina mentre stai studiando. In questo caso, puoi smettere di studiare e goderti la pizza. In secondo luogo, finisci il tuo studio presto e dormi (cioè, il tuo lavoro assegnato per oggi – studi per l’esame finale di domani – è fatto) prima che la pizza sia disponibile. Certo, non puoi dormire; altrimenti, non avrai la possibilità di mangiare la pizza. Quello che farai è aspettare che tuo fratello riporti la pizza.

Come nell’esempio, i due casi danno un significato di rivalità.