C #: Hvad er forskellen mellem tråd-sikker og atomisk?


Svar 1:

Trådsikre midler bliver ikke rodet, når man får adgang fra flere tråde; atomisk betyder udelelig, i den sammenhæng svarende til uafbrydelig.

For at implementere låse har du to valg:

  1. Har hardwarestøtte til atomoperationer - specielle sammensatte instruktioner, der udføres som en helhed, som Test- og -sæt. Bliv smart (og lide konsekvenserne) - Petersons algoritme.

I dit eksempel i detaljerne er begge usikre; hvis jeg forstod korrekt, mener du noget i denne retning:

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

    offentlig int Unsafe1 {get; sæt; } = 0;

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

        sæt
        {
            lås (ulock)
            {
                _unsafe2 = værdi;
            }
        }
    }
}

Testkode:

var u = ny Usikker ();

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

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

Resultat (en af ​​mange mulige):

4648265 - 4149827

For begge forsvandt mere end halvdelen af ​​opdateringerne.

Årsagen er, at ++ ikke er atomisk - det er faktisk tre separate operationer:

  1. Hent værdi.Læg 1 til værdi.Sæt værdi.

Vi kan løse dette ved at tilvejebringe en inkrementoperation, der er atomisk - der er mange måder at gøre det på, men her er to:

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

    offentlig int Safe1 {get; sæt; }
    public void SafeIncrement1 ()
    {
        lås (ulock)
        {
            this.Safe1 ++;
        }
    }

    privat int _safe2 = 0;
    offentlig int Safe2
    {
        få
        {
            retur _safe2;
        }
        sæt
        {
            _safe2 = værdi;
        }
    }
    public void SafeIncrement2 ()
    {
        Interlocked.Inrement (ref _safe2);
    }
}

Testkode:

var s = new Safe ();

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

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

Resultaterne er korrekte i begge tilfælde. Den første sætter bare en lås omkring hele den sammensatte ++ operation, mens den anden bruger hardwarestøtte til atomoperationer.

Bemærk den anden variant ovenfor med Interlocked.Crement, er meget hurtigere, men er faktisk et lavere niveau og begrænset hvad det kan gøre ud af boksen; operationerne i den Interlocked-pakke kan dog bruges til at implementere:

  1. De velkendte låse - kaldet “pessimistisk samtidighed”, fordi de antager, at operationen vil blive afbrudt, så gider ikke starte, før de har erhvervet en delet ressource. “Låsefri kode”, f.eks. “Optimistisk samtidighed” - ved hjælp af f.eks. Sammenlign-og-swap, du bruger en speciel "kanarie" -værdi, som du registrerer i starten, og sørg derefter for, at der ikke er ændret under dig i slutningen; tanken er, at hvis en anden tråd kommer med, vil den dræbe kanariefuglen, så du ved at prøve igen din transaktion fra starten. Dette kræver, at din egen kode også er atomisk - du kan ikke skrive mellemresultater til den delte tilstand, du skal enten lykkes helt eller mislykkes fuldstændigt (som om du ikke har udført nogen operationer).

Svar 2:

To helt forskellige ting. Tråd sikkert betyder en funktion, der er skrevet på en sådan måde, at den gentagne gange kan kaldes af mange forskellige tråde, uden at hver tråd roterer operationen af ​​en anden tråd (for eksempel ved at ændre værdien, hvis en variabel, som en anden tråd bruger)

Atom betyder (hvis jeg kommer dit sted du går med dette) oprettelse af en forekomst af et objekt - så uanset hvor ofte det er henvist til, ser du altid den ene instans (fra en hvilken som helst tråd)


Svar 3:

Atomoperationer er en måde at opnå gevindsikkerhed på enten ved hjælp af en slags låse som Mutexes eller Semaphores, der bruger atomoperationer internt eller ved at implementere låsefri synkronisering ved hjælp af atom- og hukommelseshegn.

Atomoperationer på primitive datatyper er således et værktøj til at opnå tråd sikkerhed, men sikrer ikke tråd sikkerhed automatisk, fordi du normalt har flere operationer, der er afhængige af hinanden. Du skal sikre dig, at disse operationer udføres uden afbrydelse, f.eks. Ved hjælp af Mutexes.

Ja, skrivning af en af ​​disse atomdatatyper i c # er tredsikker, men det gør ikke den funktion, du bruger dem, til tråd-sikker. Det sikrer kun, at den enkelte skrivning udføres korrekt, selvom en anden tråd har adgang til den "på samme tid". Ikke desto mindre er den næste aflæsning fra den aktuelle tråd ikke sikret for at få den værdi, der tidligere er skrevet, da en anden tråd muligvis har skrevet til den, kun for at den læste værdi er gyldig.