La oss si at det finnes en klasse som inneholder et offentlig int-tellerfelt som kan nås av flere tråder, og dette tallet vil bare øke eller synke.
Når man legger til dette feltet, hvilke av følgende skjemaer bør brukes, og hvorfor?
- lock(this.locker) this.counter++;
- Interlocked.Increment (ref this.counter);
- Endre tilgangsmodifikatoren til counter til offentlig volatil
Verst (ingen av dem fungerer egentlig)
Endre tilgangsmodifikatoren til counter til offentlig volatil
Denne tilnærmingen er faktisk ikke sikker i det hele tatt, og poenget med Volatile er at flere tråder som kjører på flere CPU-er buffrer data og omorganiserer utførte instruksjoner.
Hvis den er ikke-flyktig, må CPU B vente en stund for å se den økte verdien når CPU A øker med en verdi, noe som kan føre til problemer.
Hvis den er flyktig, sikrer det at begge CPU-ene ser samme verdi samtidig. Men det unngår ikke tverrgående lese- og skriveoperasjoner.
Å tilføre verdi til en variabel krever faktisk tre steg
1. Lesing, 2. Legg til 3. Skriv
Anta at tråd A leser verdien av telleren som 1 og ikke er klar til å øke, så leser tråd B også verdien av telleren som 1, og så begynner begge trådene å utføre inkrementelle og skriveoperasjoner. Verdien av den endelige telleren er 2. Dette stemmer ikke, begge trådene har utført en økningsoperasjon, og det riktige resultatet skal være 3. Så å merke det som flyktig er rett og slett utrygt.
Det er bedre
lock(this.locker) this.counter++;
På denne måten er det trygt (husk å låse overalt du vil for å få tilgang til denne disken, selvfølgelig). Den forhindrer at andre tråder kjører den låste koden. Og det forhindrer også problemet med multi-CPU-instruksjonssekvensering nevnt ovenfor. Problemet er at lås er treg i ytelsen, og hvis du bruker lås på andre, ikke-relaterte steder, kan det blokkere de andre trådene dine.
Best
Interlocked.Increment (ref this.counter);
Dette er trygt og veldig effektivt. Den utfører lesing, økning, skriving tre operasjoner i ett atom uten å bli avbrutt i midten. Fordi det ikke påvirker annen kode, trenger du ikke huske låser andre steder. Og det er også veldig raskt (som MSDN sier, på dagens CPU-er er det ofte bare en instruksjon).
Men jeg er ikke helt sikker på om det også kan løse CPU-ens instruksjonsrekkefølgeproblem, eller om det må brukes sammen med volatile og dette inkrementet.
Supplement: Hvilke problemer løser volatile godt?
Siden Volatile ikke kan forhindre multitråding, hva kan den gjøre? Et godt eksempel er at du har to tråder, den ene skriver alltid til en variabel, la oss si at denne variabelen er queneLength, og den andre leser alltid data fra denne variabelen. Hvis queueLenght ikke er flyktig, kan tråd A lese 5 ganger, men tråd B kan se forsinket data, eller til og med data i feil rekkefølge. En løsning er å bruke lock, men i dette tilfellet kan du også bruke volatile. Dette sikrer at tråd B alltid ser de nyeste dataene skrevet av tråd A, men denne logikken fungerer bare hvis du ikke leser det når du skriver det, og hvis du ikke skriver det når du leser det. Når flere tråder vil utføre lese-modifiser-skrive-operasjoner, må du bruke Interlocked eller lock.
|