Peggiore (in realtà non funzionerà)
Cambia il modificatore di accesso di counter
apublic volatile
Come altri hanno già detto, questo da solo non è affatto sicuro. Il punto volatile
è che più thread in esecuzione su più CPU possono e memorizzeranno nella cache i dati e riordineranno le istruzioni.
In caso contrario volatile
, e la CPU A incrementa un valore, la CPU B potrebbe effettivamente non vedere quel valore incrementato fino a qualche tempo dopo, il che potrebbe causare problemi.
In tal caso volatile
, ciò garantisce che le due CPU visualizzino gli stessi dati contemporaneamente. Non li impedisce affatto di intercalare le loro operazioni di lettura e scrittura che è il problema che stai cercando di evitare.
Il secondo migliore:
lock(this.locker) this.counter++
;
Questo è sicuro da fare (a condizione che ti ricordi in lock
qualsiasi altro luogo a cui accedi this.counter
). Impedisce a qualsiasi altro thread di eseguire qualsiasi altro codice protetto da locker
. Utilizzando anche i blocchi, si evitano i problemi di riordino multi-CPU come sopra, il che è fantastico.
Il problema è che il blocco è lento e se riutilizzi locker
in qualche altro posto che non è realmente correlato, puoi finire per bloccare gli altri thread senza motivo.
Migliore
Interlocked.Increment(ref this.counter);
Questo è sicuro, in quanto esegue effettivamente la lettura, l'incremento e la scrittura in "un colpo" che non può essere interrotto. Per questo motivo, non influirà su nessun altro codice e non è necessario ricordare di bloccare altrove. È anche molto veloce (come dice MSDN, sulle moderne CPU, questa è spesso letteralmente una singola istruzione CPU).
Non sono del tutto sicuro, tuttavia, se aggira le altre CPU riordinando le cose o se devi anche combinare volatile con l'incremento.
InterlockedNotes:
- I METODI INTERBLOCCATI SONO SICURAMENTE SICURI SU QUALSIASI NUMERO DI CORE O CPU.
- I metodi interbloccati applicano una recinzione completa attorno alle istruzioni che eseguono, quindi il riordino non avviene.
- I metodi interbloccati non necessitano o addirittura non supportano l'accesso a un campo volatile , poiché volatile viene posizionato un mezzo recinto attorno alle operazioni su un determinato campo e l'interblocco utilizza il recinto completo.
Nota in calce: ciò che è volatile è effettivamente utile.
Come volatile
che non impedisce questo tipo di problemi con il multithreading, a cosa serve? Un buon esempio sta nel dire che hai due thread, uno che scrive sempre su una variabile (diciamo queueLength
) e uno che legge sempre da quella stessa variabile.
Se queueLength
non è volatile, il thread A può scrivere cinque volte, ma il thread B può vedere quelle scritture come ritardate (o anche potenzialmente nell'ordine sbagliato).
Una soluzione sarebbe quella di bloccare, ma potresti anche usare volatile in questa situazione. Ciò garantirebbe che il thread B vedrà sempre la cosa più aggiornata che il thread A ha scritto. Nota tuttavia che questa logica funziona solo se hai scrittori che non leggono mai e lettori che non scrivono mai e se la cosa che stai scrivendo ha un valore atomico. Non appena si esegue una sola lettura-modifica-scrittura, è necessario accedere alle operazioni interbloccate o utilizzare un blocco.