C #: Vad är skillnaden mellan tråd-säker och atom?


Svar 1:

Trådskyddande medel blir inte trasslat när det nås från flera trådar; atom betyder odelbar, i det sammanhanget likvärdigt med oavbruten.

För att implementera lås har du två val:

  1. Ha hårdvarosupport för atomoperationer - speciella sammansatta instruktioner som körs som en helhet, som Test- och -set.Be smart (och drabbas av konsekvenserna) - Petersons algoritm.

I ditt exempel i detaljerna är båda osäkra; om jag förstod rätt, menar du något liknande:

allmän klass osäker
{
    privat objekt ulock = nytt objekt ();

    public int Unsafe1 {get; uppsättning; } = 0;

    privat int _unsafe2 = 0;
    public int Unsafe2
    {
        skaffa sig
        {
            lås (ulock)
            {
                tillbaka _unsafe2;
            }
        }

        uppsättning
        {
            lås (ulock)
            {
                _unsafe2 = värde;
            }
        }
    }
}

Testkod:

var u = new Osäker ();

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

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

Resultat (en av många möjliga):

4648265 - 4149827

För båda försvann mer än hälften av uppdateringarna.

Anledningen är att ++ inte är atomisk - det är faktiskt tre separata operationer:

  1. Få värde. Lägg till 1 till värde. Ställ in värde.

Vi kan fixa detta genom att tillhandahålla en tillväxtoperation som är atomisk - det finns många sätt att göra det, men här är två:

allmän klass Safe
{
    privat objekt slock = nytt objekt ();

    public int Safe1 {get; uppsättning; }
    public void SafeIncrement1 ()
    {
        lås (ulock)
        {
            this.Safe1 ++;
        }
    }

    privat int _safe2 = 0;
    public int Safe2
    {
        skaffa sig
        {
            tillbaka _safe2;
        }
        uppsättning
        {
            _safe2 = värde;
        }
    }
    public void SafeIncrement2 ()
    {
        Interlocked.Inrement (ref _safe2);
    }
}

Testkod:

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));

Resultaten är korrekta i båda fallen. Den första sätter bara ett lås runt hela komposit ++ -operationen, medan den andra använder hårdvarustöd för atomoperationer.

Observera att den andra varianten ovan, med Interlocked.Crement, är mycket snabbare, men är faktiskt lägre nivå och begränsad vad det kan göra ur lådan; emellertid kan operationerna i det sammanlänkade paketet användas för att implementera:

  1. De välkända låsen - kallade "pessimistisk samtidighet" eftersom de antar att operationen kommer att avbrytas, så bry dig inte om förrän de har skaffat någon delad resurs. "Låsfri kod", a.k. "optimistisk samtidighet" - med t.ex. Jämför och byt, du använder ett speciellt "kanarie" -värde som du registrerar i början, och se sedan till att det inte har ändrats under dig i slutet; idén är att om en annan tråd kommer med kommer den att döda kanariefågeln, så du vet att försöka igen din transaktion från början. Detta kräver att din egen kod också är atomisk - du kan inte skriva mellanliggande resultat till det delade tillståndet, du måste antingen lyckas helt eller misslyckas helt (som om du inte utfört några operationer).

Svar 2:

Två helt olika saker. Trådskydd betyder en funktion som är skriven på ett sådant sätt att den kan upprepas upprepade gånger av många olika trådar, utan att varje tråd trasiga upp operationen för en annan tråd (till exempel genom att ändra värdet om en variabel som en annan tråd använder)

Atom betyder (om jag kommer dit du ska med detta) att skapa en instans av ett objekt - så oavsett hur ofta det hänvisas så ser du alltid den ena instansen (från vilken tråd som helst)


Svar 3:

Atomoperationer är ett sätt att uppnå gängesäkerhet antingen genom att använda någon form av lås som Mutexes eller Semafhores som använder atomoperationer internt eller genom att implementera låsfri synkronisering med hjälp av atom- och minnesstaket.

Så atomoperationer på primitiva datatyper är ett verktyg för att uppnå gängesäkerhet men garanterar inte gängesäkerhet automatiskt eftersom du normalt har flera operationer som litar på varandra. Du måste se till att dessa operationer utförs utan avbrott, t.ex. med hjälp av Mutexes.

Ja, att skriva en av dessa atomdatatyper i c # är trådsäker, men det gör inte funktionen du använder dem i tråd säker. Det säkerställer bara att det enda skrivet körs korrekt även om en andra tråd har åtkomst till den "på samma gång". Men inte mindre, nästa läsning från den aktuella tråden säkerställs inte för att få värdet som tidigare skrivits eftersom en annan tråd kan ha skrivit till den, bara för att det lästa värdet är giltigt.