C #: Hva er forskjellen mellom trådsikker og atomisk?


Svar 1:

Tråd-trygge midler blir ikke rotete når du får tilgang fra flere tråder; atom betyr udelbar, i den sammenhengen som tilsvarer uavbrutt.

For å implementere låser har du to valg:

  1. Ha maskinvarestøtte for atomoperasjoner - spesielle sammensatte instruksjoner som kjøres som en helhet, som Test- og-sett.Ber smart (og lider konsekvensene) - Petersons algoritme.

I eksempelet ditt i detaljene er begge usikre; hvis jeg forsto riktig, mener du noe som dette:

offentlig klasse Usikker
{
    privat objekt ulock = nytt objekt ();

    offentlig int Unsafe1 {get; sett; } = 0;

    privat int _unsafe2 = 0;
    offentlig int Unsafe2
    {
        få
        {
            lås (ulock)
            {
                retur _unsafe2;
            }
        }

        sett
        {
            lås (ulock)
            {
                _unsafe2 = verdi;
            }
        }
    }
}

Testkode:

var u = new Usikker ();

Parallell.For (0, 10000000, _ => {u.Unsafe1 ++;});
Parallell.For (0, 10000000, _ => {u.Unsafe2 ++;});

Console.WriteLine (string.Format ("{0} - {1}", u.Unsafe1, u.Unsafe2));

Resultat (ett av mange mulige):

4648265 - 4149827

For begge forsvant mer enn halvparten av oppdateringene.

Årsaken er at ++ ikke er atomisk - det er faktisk tre separate operasjoner:

  1. Få verdi.Legg til 1 til verdi.Sett verdi.

Vi kan fikse dette ved å tilby en økning som er atomisk - det er mange måter å gjøre det på, men her er to:

offentlig klasse Safe
{
    privat objekt slock = nytt objekt ();

    offentlig int Safe1 {få; sett; }
    public void SafeIncrement1 ()
    {
        lås (ulock)
        {
            this.Safe1 ++;
        }
    }

    privat int _safe2 = 0;
    offentlig int Safe2
    {
        få
        {
            retur _safe2;
        }
        sett
        {
            _safe2 = verdi;
        }
    }
    public void SafeIncrement2 ()
    {
        Interlocked.Inrement (ref _safe2);
    }
}

Testkode:

var s = new Safe ();

Parallell.For (0, 10000000, _ => {s.SafeIncrement1 ();});
Parallell.For (0, 10000000, _ => {s.SafeIncrement2 ();});

Console.WriteLine (string.Format ("{0} - {1}", s.Safe1, s.Safe2));

Resultatene er riktige i begge tilfeller. Den første setter bare en lås rundt hele kompositt ++ -operasjonen, mens den andre bruker maskinvarestøtte for atomoperasjoner.

Legg merke til den andre varianten over, med Interlocked.Inrrement, er mye raskere, men er faktisk lavere nivå og begrenset i hva den kan gjøre ut av boksen; imidlertid kan operasjonene i Interlocked-pakken brukes til å implementere:

  1. De kjente låsene - kalt “pessimistisk samtidighet” fordi de antar at operasjonen vil bli avbrutt, så ikke gidder å starte før de har skaffet seg en delt ressurs. “Låsefri kode”, a.k. “optimistisk samtidighet” - ved å bruke f.eks. Sammenlign-og-bytt, du bruker en spesiell "kanarifugl" -verdi som du registrerer i starten, og pass på at ikke har endret seg under deg på slutten; tanken er at hvis en annen tråd kommer, vil den drepe kanari, så du vet å prøve transaksjonen på nytt fra starten. Dette krever at din egen kode også er atomisk - du kan ikke skrive mellomresultater til den delte staten, du må enten lykkes helt eller mislykkes fullstendig (som om du ikke utførte noen operasjoner).

Svar 2:

To helt forskjellige ting. Trådsikker betyr en funksjon skrevet på en slik måte at den kan kalles gjentatte ganger av mange forskjellige tråder, uten at hver tråd roter opp driften til en annen tråd (for eksempel ved å endre verdien hvis en variabel som en annen tråd bruker)

Atom betyr (hvis jeg kommer dit du skal med dette) å lage en forekomst av et objekt - så uansett hvor ofte det er referert til, ser du alltid den ene forekomsten (fra hvilken som helst tråd)


Svar 3:

Atomoperasjoner er en måte å oppnå tråden sikkerhet enten ved å bruke noen form for låser som Mutexes eller Semafhores som bruker atomoperasjoner internt eller ved å implementere låsefri synkronisering ved hjelp av atom- og minnegjerder.

Atomoperasjoner på primitive datatyper er altså et verktøy for å oppnå tråden sikkerhet, men ikke sikre tråd sikkerhet automatisk fordi du normalt har flere operasjoner som er avhengige av hverandre. Du må sørge for at disse operasjonene utføres uten avbrudd, for eksempel ved bruk av Mutexes.

Ja, å skrive en av disse atomdatatypene i c # er trådsikker, men det gjør ikke at funksjonen du bruker dem i trådsikker. Det sikrer bare at den enkelte skrivingen blir korrekt utført selv om en andre tråd har tilgang til den "på samme tid". Ikke desto mindre er ikke den neste avlesningen fra den gjeldende tråden sikret for å få verdien som tidligere er skrevet, ettersom en annen tråd kanskje har skrevet til den, bare at verdien som er lest, er gyldig.