Låt oss säga att det finns en klass som innehåller ett publikt int-räknarfält som kan nås av flera trådar, och detta antal kommer bara att öka eller minska.
När man lägger till detta fält, vilka av följande system bör användas och varför?
- lock(this.locker) this.counter++;
- Interlocked.Increment(ref this.counter);
- Ändra åtkomstmodifieraren för counter till offentlig volatil
Värst (ingen av dem fungerar egentligen)
Ändra åtkomstmodifieraren för counter till offentlig volatil
Denna metod är faktiskt inte alls säker, och poängen med volatile är att flera trådar som körs på flera CPU:er buffrar data och omarrangerar utförda instruktioner.
Om det är icke-flyktigt måste CPU B vänta ett tag när CPU A ökar med ett värde, vilket kan leda till problem.
Om den är volatil säkerställer det att båda CPU:erna ser samma värde samtidigt. Men det undviker inte tvärgående läs- och skrivoperationer.
Att tillföra värde till en variabel tar faktiskt tre steg
1. Läsning, 2. Lägg till 3. Skriv
Antag att tråd A läser värdet på räknaren som 1 och inte är redo att öka, då läser tråd B också värdet på räknaren som 1, och då börjar båda trådarna utföra inkrementella och skrivoperationer. Värdet på den slutliga räknaren är 2. Detta stämmer inte, båda trådarna har gjort en ökningsoperation, och det korrekta resultatet borde vara 3. Så att märka det som flyktigt är helt enkelt osäkert.
Det är bättre
lock(this.locker) this.counter++;
På så sätt är det säkert (kom ihåg att låsa överallt där du vill komma åt denna disk, förstås). Det förhindrar att någon annan tråd kan köra den låsta koden. Och det förhindrar också det multi-CPU-instruktionssekvenseringsproblem som nämnts ovan. Problemet är att låsning är långsam i prestanda, och om du använder lås på andra orelaterade ställen kan det blockera dina andra trådar.
Bäst
Interlocked.Increment(ref this.counter);
Detta är säkert och mycket effektivt. Den utför läs-, ökande- och skrivoperationer i en atom utan att avbrytas i mitten. Eftersom det inte påverkar annan kod behöver du inte komma ihåg lås någon annanstans. Och det är också väldigt snabbt (som MSDN säger, på dagens CPU:er är det ofta bara en instruktion).
Men jag är inte helt säker på om det också kan lösa CPU:ns instruktionsordningsproblem, eller om det behöver användas tillsammans med volatile och detta inkrement.
Tillskott: Vilka problem löser flyktigt bra innehåll?
Eftersom Volatile inte kan förhindra multitrådning, vad kan den göra? Ett bra exempel är att du har två trådar, en skriver alltid till en variabel, låt oss säga att denna variabel är queneLength, och den andra läser alltid data från denna variabel. Om queueLenght inte är flyktig kan tråd A läsa 5 gånger, men tråd B kan se fördröjd data, eller till och med data i fel ordning. En lösning är att använda lås, men i det här fallet kan du också använda volatil. Detta säkerställer att tråd B alltid ser den senaste datan skriven av tråd A, men denna logik fungerar bara om du inte läser den när du skriver den, och om du inte skriver den när du läser den. När flera trådar vill göra läs-modifiera-skriv-operationer behöver du använda Interlocked eller lock.
|