C #: Jaký je rozdíl mezi bezpečností vláken a atomovými?


Odpověď 1:

Prostředky bezpečné pro vlákno se nezachytí při přístupu z více vláken; atomový znamená nedělitelný, v tomto kontextu rovnocenný nepřerušitelnému.

Chcete-li implementovat zámky, máte dvě možnosti:

  1. Mají hardwarovou podporu pro atomové operace - speciální složené instrukce, které se provádějí jako celek, jako Test-and-set.Be smart (a trpí následky) - Petersonův algoritmus.

Ve vašem příkladu v detailech jsou oba nebezpečné; pokud jsem to pochopil správně, máte na mysli něco takového:

veřejná třída nebezpečná
{
    soukromý objekt ulock = nový objekt ();

    public int Unsafe1 {get; soubor; } = 0;

    soukromý int _unsafe2 = 0;
    public int Unsafe2
    {
        dostat
        {
            zámek (ulock)
            {
                return _unsafe2;
            }
        }

        soubor
        {
            zámek (ulock)
            {
                _unsafe2 = hodnota;
            }
        }
    }
}

Testovací kód:

var u = new Unsafe ();

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

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

Výsledek (jeden z mnoha možných):

4648265 - 4149827

U obou zmizela více než polovina aktualizací.

Důvod je ten, že ++ není atomová - jsou to vlastně tři oddělené operace:

  1. Get value.Add 1 to value.Set value.

To můžeme napravit provedením operace přírůstku, která je atomová - existuje mnoho způsobů, jak to udělat, ale zde jsou dva:

veřejná třída bezpečná
{
    soukromý objekt slock = nový objekt ();

    public int Safe1 {get; soubor; }
    public void SafeIncrement1 ()
    {
        zámek (ulock)
        {
            this.Safe1 ++;
        }
    }

    soukromý int _safe2 = 0;
    public int Safe2
    {
        dostat
        {
            return _safe2;
        }
        soubor
        {
            _safe2 = hodnota;
        }
    }
    public void SafeIncrement2 ()
    {
        Interlocked.Increment (ref _safe2);
    }
}

Testovací kód:

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

Výsledky jsou v obou případech správné. První právě staví zámek kolem celé operace kompozitní ++, zatímco druhý používá hardwarovou podporu pro atomové operace.

Všimněte si, že druhá výše uvedená varianta, s Interlocked.Increment, je mnohem rychlejší, ale je ve skutečnosti nižší úroveň a omezená v tom, co může dělat mimo krabici; operace v balíčku Interlocked však lze použít k implementaci:

  1. Známé zámky - nazývané „pesimistická souběžnost“, protože předpokládají, že se operace přeruší, neobtěžujte se začít, dokud nezískají nějaký sdílený zdroj. „Kód bez zámku“, a.k.a. Pro srovnání a výměnu použijete speciální hodnotu „canary“, kterou zaznamenáte na začátku, a pak se ujistěte, že se na konci nezměnila; myšlenka je, že pokud přijde další vlákno, to zabije kanárik, takže víte, že zkuste transakci od začátku. To vyžaduje, aby byl váš atomový kód také atomový - do sdíleného stavu nemůžete zapisovat průběžné výsledky, musíte buď úplně uspět, nebo úplně selhat (jako byste neprováděli žádné operace).

Odpověď 2:

Dvě zcela odlišné věci. Zabezpečení podprocesu znamená funkci zapsanou tak, že ji lze opakovaně volat mnoha různými podprocesy, aniž by každé vlákno narušovalo provoz jiného vlákna (například změnou hodnoty, pokud proměnná, kterou používá jiné vlákno)

Atomový znamená (pokud se dostanu tam, kam jdete) vytvořením jedné instance objektu - takže bez ohledu na to, jak často se na to odkazuje, vždy uvidíte tu jednu instanci (z jakéhokoli vlákna)


Odpověď 3:

Atomové operace jsou způsob, jak dosáhnout bezpečnosti vláken buď pomocí některých zámků, jako jsou Mutexes nebo Semafory, které používají atomové operace interně, nebo implementací synchronizace bez zámku pomocí atomových a paměťových plotů.

Atomové operace na primitivních typech dat jsou tedy nástrojem k dosažení bezpečnosti vláken, ale nezajišťují bezpečnost vláken automaticky, protože obvykle máte více operací, které se na sebe spoléhají. Musíte zajistit, aby tyto operace byly prováděny bez přerušení, např. Pomocí mutexů.

Ano, zápis jednoho z těchto typů atomových dat do c # je bezpečný pro vlákno, ale to neznamená, že funkce, kterou používáte, je pro vlákno bezpečný. Zajišťuje pouze, že je jednorázový zápis proveden správně, i když k němu přistupuje druhé vlákno „současně“. Přesto není další čtení z aktuálního vlákna zajištěno, aby bylo možné získat dříve zapsanou hodnotu, protože na ni mohlo zapsat jiné vlákno, pouze pokud je přečtená hodnota platná.