Řekněme, že existuje třída, která obsahuje veřejné pole čítače int, ke kterému může přistupovat více vláken, a toto číslo se bude jen zvyšovat nebo snižovat.
Při přidávání tohoto pole, které z následujících schémat by měly být použity a proč?
- lock(this.locker) this.counter++;
- Propojené. Postupné (odkaz na toto.čítač);
- Změňte přístupový modifikátor counter na public volatile
Nejhorší (žádná z nich vlastně nefunguje)
Změňte přístupový modifikátor counter na public volatile
Tento přístup není vůbec bezpečný a smyslem volatile je, že více vláken běžících na více CPU uchovává data v bufferu a přeskupuje vykonávané instrukce.
Pokud je nevolatilní, když CPU A vzroste o hodnotu, CPU B musí chvíli počkat, než se hodnota zvýší, což může vést k problémům.
Pokud je volatilní, zajistí, že oba CPU vidí stejnou hodnotu současně. Ale nevyhne se průřezovým operacím čtení a zápisu.
Přidání hodnoty proměnné ve skutečnosti vyžaduje tři kroky
1. Čtení, 2. Přičíst 3. Napsat
Předpokládejme, že vlákno A čte hodnotu čítače jako 1 a není připraveno k navýšení, pak vlákno B také čte hodnotu čítače jako 1, a obě vlákna začnou provádět inkrementální a zápisové operace. Hodnota posledního žetonu je 2. To není správné, obě vlákna provedla operaci zvyšování a správný výsledek by měl být 3. Takže označovat ho jako volatilní je prostě nebezpečné.
Je to lepší
lock(this.locker) this.counter++;
Tímto způsobem je to bezpečné (nezapomeňte zamknout všechna místa, kde chcete přistupovat k tomuto pultu, samozřejmě). Zabraňuje tomu, aby jakékoli jiné vlákno mohlo vykonat zamčený kód. A také zabraňuje problému s řadou instrukcí více CPU, který byl zmíněn výše. Problém je, že zámek je pomalý ve výkonu, a pokud použijete zámek na jiných nesouvisejících místech, může blokovat vaše ostatní vlákna.
Nejlepší
Propojené. Postupné (odkaz na toto.čítač);
To je bezpečné a velmi efektivní. Provádí čtení, zvyšování a zápis tří operací v jednom atomu bez přerušení uprostřed. Protože to neovlivňuje jiný kód, nemusíte si zámky pamatovat jinde. A je také velmi rychlý (jak říká MSDN, na dnešních CPU je to často jen instrukce).
Ale nejsem si úplně jistý, jestli to může vyřešit i problém s pořadím instrukcí CPU, nebo jestli je potřeba to používat společně s Volatile a tímto inkrementem.
Doplněk: Jaké problémy Volatile řeší dobře?
Jelikož Volatile nemůže zabránit vícevláknovému zpracování, co dokáže? Dobrým příkladem jsou dvě vlákna, jedno vždy zapisuje do proměnné, řekněme, že tato proměnná je queneLength, a druhé vždy čte data z této proměnné. Pokud délka fronty není volatilní, vlákno A může číst 5krát, ale vlákno B může vidět zpožděná data, nebo dokonce data v nesprávném pořadí. Jedním řešením je použít zámek, ale v tomto případě můžete použít i volatile. To zajišťuje, že vlákno B vždy vidí nejnovější data napsaná vláknem A, ale tato logika funguje jen tehdy, pokud je při zápisu nečtete a když je nezapisujete. Jakmile chce více vláken provádět operace čtení-úprava-zápis, je potřeba použít Interlocked nebo lock.
|