Lad os sige, at der findes en klasse, der indeholder et offentligt int-tællefelt, som kan tilgås af flere tråde, og dette tal kun vil stige eller falde.
Når man tilføjer dette felt, hvilke af følgende skemaer bør så bruges, og hvorfor?
- lock(this.locker) this.counter++;
- Interlocked.Increment(ref this.counter);
- Skift adgangsmodifikatoren for counter til offentlig volatil
Værst (ingen af dem virker egentlig)
Skift adgangsmodifikatoren for counter til offentlig volatil
Denne tilgang er faktisk slet ikke sikker, og pointen med Volatile er, at flere tråde, der kører på flere CPU'er, buffer data og omarrangerer udførte instruktioner.
Hvis den er ikke-flygtig, skal CPU B vente et stykke tid for at se den øgede værdi, når CPU A stiger med en værdi, hvilket kan føre til problemer.
Hvis det er flygtigt, sikrer det, at begge CPU'er ser samme værdi på samme tid. Men det undgår ikke tværgående læse- og skriveoperationer.
At tilføre værdi til en variabel kræver faktisk tre trin
1. Læsning, 2. Tilføj 3. Skriv
Antag, at tråd A læser værdien af tælleren som 1 og ikke er klar til at øge, så læser tråd B også værdien af tælleren som 1, og så begynder begge tråde at udføre inkrementelle og skriveoperationer. Værdien af den endelige tæller er 2. Det er ikke korrekt, begge tråde har udført en forøgelsesoperation, og det korrekte resultat burde være 3. Så at mærke det som flygtigt er simpelthen usikkert.
Det er bedre
lock(this.locker) this.counter++;
På den måde er det sikkert (husk selvfølgelig at låse overalt, hvor du vil have adgang til denne disk). Det forhindrer andre tråde i at udføre den låste kode. Og det forhindrer også problemet med multi-CPU instruktionssekventering nævnt ovenfor. Problemet er, at lock er langsom i ydeevnen, og hvis du bruger lock andre steder, der ikke er relateret, kan det blokere dine andre tråde.
Bedst
Interlocked.Increment(ref this.counter);
Det er sikkert og meget effektivt. Den udfører læse-, øgnings- og skriveoperationer i ét atom uden at blive afbrudt midt imellem. Fordi det ikke påvirker anden kode, behøver du ikke huske låse andre steder. Og det er også meget hurtigt (som MSDN siger, er det på nutidens CPU'er ofte bare en instruktion).
Men jeg er ikke helt sikker på, om det også kan løse CPU'ens instruktionsordensproblem, eller om det skal bruges sammen med volatile og dette inkrement.
Supplement: Hvilke problemer løser volatile godt?
Da Volatile ikke kan forhindre multithreading, hvad kan den så gøre? Et godt eksempel er, at du har to tråde, den ene skriver altid til en variabel, lad os sige, at denne variabel er queneLength, og den anden læser altid data fra denne variabel. Hvis queueLenght ikke er flygtig, kan tråd A læse 5 gange, men tråd B kan se forsinkede data eller endda data i forkert rækkefølge. En løsning er at bruge lock, men i dette tilfælde kan du også bruge volatile. Dette sikrer, at tråd B altid ser de seneste data skrevet af tråd A, men denne logik virker kun, hvis du ikke læser den, når du skriver den, og hvis du ikke skriver den, når du læser den. Når flere tråde vil udføre læs-ændre-skrive-operationer, skal du bruge Interlocked eller lock.
|