Recimo, da obstaja razred, ki vsebuje javno polje števca int, do katerega lahko dostopa več niti, in to število se bo samo povečevalo ali zmanjševalo.
Pri dodajanju tega polja, katere od naslednjih shem naj uporabimo in zakaj?
- lock(this.locker) this.counter++;
- Interlocked.Increment (ref this.counter);
- Spremeni modifikator dostopa v counter na javno volatile
Najslabše (nobena od njih dejansko ne deluje)
Spremeni modifikator dostopa v counter na javno volatile
Ta pristop pravzaprav sploh ni varen, in bistvo volatile je, da več niti, ki tečejo na več procesorjih, medpomnijo podatke in preurejajo izvedene ukaze.
Če je nehlapna, mora CPU B počakati nekaj časa, ko CPU A poveča vrednost, kar lahko povzroči težave.
Če je hlapna, zagotavlja, da oba procesorja hkrati zaznata enako vrednost. Vendar pa ne preprečuje presečnih operacij branja in pisanja.
Dodajanje vrednosti spremenljivki dejansko zahteva tri korake
1. Branje, 2. Dodaj 3. Napiši
Predpostavimo, da nit A prebere vrednost števca kot 1 in ni pripravljena povečati, potem nit B prav tako prebere vrednost števca kot 1, nato pa obe niti začneta izvajati inkrementalne in zapisovalne operacije. Vrednost zadnjega števca je 2. To ni pravilno, obe niti sta izvedli operacijo povečanja, pravilen rezultat pa bi moral biti 3. Torej je označevanje kot hlapnega preprosto nevarno.
Tako je bolje
lock(this.locker) this.counter++;
Tako je varno (seveda ne pozabite zakleniti povsod, kjer želite dostopati do tega pulta). Preprečuje, da bi katera koli druga nit izvajala zaklenjeno kodo. Prav tako preprečuje težave z zaporedjem ukazov z več procesorji, omenjene zgoraj. Težava je v tem, da je zaklepanje počasno v zmogljivosti, in če zaklepate na drugih nepovezanih mestih, lahko blokira druge niti.
Najboljše
Interlocked.Increment (ref this.counter);
To je varno in zelo učinkovito. Izvaja tri operacije branja, povečanja in pisanja v enem atomu brez prekinitve na sredini. Ker to ne vpliva na drugo kodo, ti ni treba zapomniti ključavnic drugje. Poleg tega je zelo hiter (kot pravi MSDN, je na današnjih procesorjih pogosto le ukaz).
Nisem pa povsem prepričan, ali lahko to reši tudi težavo z vrstnim redom ukazov procesorja ali pa ga je treba uporabljati skupaj s Volatile in tem inkrementom.
Dodatek: Katere probleme Volatile dobro rešuje?
Ker Volatile ne more preprečiti večnitnosti, kaj lahko stori? Dober primer je, da imate dve niti, ena vedno piše v spremenljivko, recimo, da je ta spremenljivka queneLength, druga pa vedno bere podatke iz te spremenljivke. Če dolžina čakalne vrste ni nestanovitna, lahko nit A bere 5-krat, nit B pa lahko zazna zamujene podatke ali celo podatke v napačnem vrstnem redu. Ena rešitev je uporaba zaklepanja, v tem primeru pa lahko uporabite tudi volatile. To zagotavlja, da nit B vedno vidi najnovejše podatke, ki jih je zapisala nit A, vendar ta logika deluje le, če jih ne berete med pisanjem in če jih ne zapišete ob branju. Ko več niti želi izvajati operacije branja, spreminjanja in pisanja, morate uporabiti Interlocked ali lock.
|