Povedzme, že existuje trieda, ktorá obsahuje verejné pole čítača int, ku ktorému môže pristupovať viacero vlákien, a toto číslo sa bude len zvyšovať alebo znižovať.
Pri pridávaní tohto poľa, ktoré z nasledujúcich schém by sa mali použiť a prečo?
- lock(this.locker) this.counter++;
- Interlocked.Increment (ref this.counter);
- Zmeňte modifikátor prístupu z counter na verejný volatile
Najhoršie (žiadny z nich vlastne nefunguje)
Zmeňte modifikátor prístupu z counter na verejný volatile
Tento prístup v skutočnosti vôbec nie je bezpečný a pointa volatile je, že viaceré vlákna bežiace na viacerých CPU ukladajú dáta do vyrovnávacej pamäte a preskupujú vykonané inštrukcie.
Ak je nevolatilný, keď CPU A vzrastie o hodnotu, CPU B musí chvíľu počkať, kým sa hodnota zvýši, čo môže viesť k problémom.
Ak je to volatilné, zabezpečuje to, že oba CPU vidia rovnakú hodnotu súčasne. Ale nevyhne sa krížovým operáciám čítania a zápisu.
Pridanie hodnoty k premennej v skutočnosti zahŕňa tri kroky
1. Čítanie, 2. Pridaj 3. Píš
Predpokladajme, že vlákno A číta hodnotu čítača ako 1 a nie je pripravené na zvýšenie, potom vlákno B tiež číta hodnotu čítača ako 1, a potom obe vlákna začnú vykonávať inkrementálne a zápisné operácie. Hodnota posledného čítača je 2. To nie je správne, obe vlákna vykonali operáciu zvyšovania a správny výsledok by mal byť 3. Takže označovať ho za nestály je jednoducho nebezpečné.
Je to lepšie
lock(this.locker) this.counter++;
Takto je to bezpečné (samozrejme, nezabudnite zamknúť všade, kde chcete pristupovať k tomuto pultu). Zabráni akémukoľvek inému vláknu vykonať uzamknutý kód. A tiež zabraňuje problému sekvencovania inštrukcií s viacprocesormi, ktorý bol spomenutý vyššie. Problém je, že zámok je pomalý vo výkone, a ak použijete zámok na iných nesúvisiacich miestach, môže zablokovať vaše ostatné vlákna.
Najlepšie
Interlocked.Increment (ref this.counter);
Je to bezpečné a veľmi efektívne. Vykonáva čítanie, zvyšovanie a zápis troch operácií v jednom atóme bez prerušenia v strede. Keďže to neovplyvňuje iný kód, nemusíš si pamätať zámky inde. A je to tiež veľmi rýchle (ako MSDN hovorí, na dnešných CPU je to často len inštrukcia).
Ale nie som si úplne istý, či to dokáže vyriešiť aj problém s poradím inštrukcií CPU, alebo či je potrebné ho používať spolu s Volatile a týmto inkrementom.
Doplnok: Aké problémy Volatile rieši dobre?
Keďže Volatile nemôže zabrániť multithreadingu, čo dokáže? Dobrým príkladom sú dve vlákna, jedno vždy zapisuje do premennej, povedzme, že táto premenná je queneLength, a druhé vždy číta dáta z tejto premennej. Ak dĺžka queueLongt nie je volatilná, vlákno A môže čítať 5-krát, ale vlákno B môže vidieť oneskorené dáta alebo dokonca údaje v nesprávnom poradí. Jedným riešením je použiť zámok, ale v tomto prípade môžete použiť aj volatile. To zabezpečuje, že vlákno B vždy vidí najnovšie dáta zapísané vláknom A, ale táto logika funguje len vtedy, ak ich nečítate pri písaní a ak ich nezapisujete pri čítaní. Keď chce viacero vlákien vykonávať operácie čítania, úpravy-zápisu, musíte použiť Interlocked alebo lock.
|