Tegyük fel, hogy van egy olyan osztály, amely tartalmaz egy nyilvános int számláló mezőt, amelyet több szál is elérhető, és ez a szám csak nő vagy csökken.
A mező hozzáadásakor melyik alábbi sémát érdemes használni, és miért?
- lock(this.locker) this.counter++;
- Interlocked.Increment (hivatkozás: this.counter);
- A counter hozzáférési módosítóját public volatile-ra változtasd
A legrosszabb (egyik sem működik igazán)
A counter hozzáférési módosítóját public volatile-ra változtasd
Ez a megközelítés valójában egyáltalán nem biztonságos, és a volatilitás lényege, hogy több szál több CPU-n puffereli az adatokat és átrendezi a végrehajtott utasításokat.
Ha nem volatilis, amikor a CPU A értékkel nő, a CPU B-nek várnia kell egy ideig, hogy lássa a megnövekedett értéket, ami problémákhoz vezethet.
Ha ingadozó, akkor mindkét CPU ugyanazt az értéket látja egyszerre. De nem kerüli el az olvasási és írási műveleteket átvágó műveleteket.
Egy változó értékének hozzáadása valójában három lépést igényel
1. Olvasás, 2. Add hozzá 3. Írj
Tegyük fel, hogy az A szál 1-ként olvassa fel a számláló értékét, és nem áll készen a növekedésre, akkor a B szál is 1-ként olvassa fel a számláló értékét, és mindkét szál elkezd inkrementális és írási műveleteket végrehajtani. A végső számláló értéke 2. Ez nem helyes, mindkét szál végzett egy növelő műveletet, és a helyes eredménynek 3 kell lennie. Tehát a lengőségnek való címkézése egyszerűen veszélyes.
Jobb
lock(this.locker) this.counter++;
Így biztonságos (természetesen ne felejtsd el zárni mindenhol, ahol hozzá akarsz férni ehhez a .counterhoz). Ez megakadályozza, hogy bármely más szál végrehajtsa a zárt kódot. Emellett megelőzi a fent említett többprocesszos utasításszekvenálási problémát is. A probléma az, hogy a lock lassú teljesítményű, és ha más, nem kapcsolódó helyeken is használod a lockot, az elzárhatja a többi szálaidat.
Legjobb
Interlocked.Increment (hivatkozás: this.counter);
Ez biztonságos és nagyon hatékony. Egy atomban három olvasási, növelés- és írásműveletet végez anélkül, hogy a közepén megszakítaná. Mivel ez más kódokat nem érint, nem kell máshol zárokat megjegyezned. És nagyon gyors is (ahogy az MSDN mondja, a mai CPU-kon gyakran csak egy utasítás).
De nem vagyok teljesen biztos benne, hogy ez megoldhatja-e a CPU utasítássorrendi problémáját is, vagy szükség van-e a volatile és ezzel a fokozattal együtt használni.
Kiegészítés: Milyen problémákat olda meg jól a volatile?
Mivel a volatile nem tudja megakadályozni a multithreadinget, mit tehet? Jó példa, ha két szálad van, az egyik mindig egy változóhoz ír, tegyük fel, hogy ez a változó queneLength, a másik pedig mindig adatokat olvas ettől a változótól. Ha a queueLenght nem ingadozék, az A szál ötször olvashat, de a B szál késleltetett adatokat vagy akár rossz sorrendben is láthat. Az egyik megoldás a lock használata, de ebben az esetben lehet volatile is használni. Ez biztosítja, hogy a B szál mindig lássa a szál A által írt legfrissebb adatokat, de ez a logika csak akkor működik, ha nem olvasod el az írás közben, és ha nem írod meg, amikor olvasod. Ha több szál olvasás-módosítás-írás műveleteket akar végezni, akkor Interlocked vagy Lock műveleteket kell használni.
|