Peggiore (in realtà non funzionerà)
Cambia il modificatore di accesso di counterapublic 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 lockqualsiasi 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 lockerin 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.