Czy dostęp do zmiennej w C# jest operacją atomową?

Zostałem wychowany w przekonaniu, że jeśli wiele wątków może uzyskać dostęp do zmiennej, to wszystkie odczyty i zapisy do tej zmiennej muszą być chronione kodem synchronizacji, takim jak instrukcja" lock", ponieważ procesor może przełączyć się na inny wątek w połowie zapisu.

Jednak przeglądałem System.Www.Ochrona.Członkostwo za pomocą reflektora i znaleziono kod w ten sposób:
public static class Membership
{
    private static bool s_Initialized = false;
    private static object s_lock = new object();
    private static MembershipProvider s_Provider;

    public static MembershipProvider Provider
    {
        get
        {
            Initialize();
            return s_Provider;
        }
    }

    private static void Initialize()
    {
        if (s_Initialized)
            return;

        lock(s_lock)
        {
            if (s_Initialized)
                return;

            // Perform initialization...
            s_Initialized = true;
        }
    }
}

Dlaczego pole s_initialized jest odczytywane poza blokadą? Couldn ' t another thread be próbujesz napisać do niego w tym samym czasie? czy odczyt i zapis zmiennych jest atomowy?

Author: Rory MacLeod, 2008-08-13

16 answers

Aby uzyskać ostateczną odpowiedź, przejdź do specyfikacji. :)

Partycja I, sekcja 12.6.6 specyfikacji CLI stwierdza: "zgodny CLI gwarantuje, że dostęp do odczytu i zapisu do odpowiednio wyrównanych miejsc pamięci, nie większych niż natywny Rozmiar słowa, jest atomowy, gdy wszystkie dostępy zapisu do lokalizacji są tego samego rozmiaru."

Więc to potwierdza, że s_Initialized nigdy nie będzie niestabilny, a odczyt i zapis do typów pierwotnych mniejszych niż 32 bity są atomowe.

W szczególności, double i long (Int64 i UInt64) są Nie gwarantowane być atomowe na platformie 32-bitowej. Możesz użyć metod z klasy Interlocked, aby je chronić.

Dodatkowo, podczas gdy odczyty i zapisy są atomowe, istnieje stan rasy z dodawaniem, odejmowaniem i zwiększaniem i zmniejszaniem typów prymitywnych, ponieważ muszą być odczytywane, operowane i przepisywane na nowo. Klasa interlocked pozwala na ich ochronę przy użyciu metod CompareExchange i Increment.

Blokowanie tworzy bariera pamięci uniemożliwiająca procesorowi zmianę kolejności odczytów i zapisów. Blokada tworzy jedyną wymaganą barierę w tym przykładzie.

 35
Author: John Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-07 09:57:33

Jest to (zła) forma wzorca blokowania podwójnego sprawdzenia, który jest , a nie wątkiem bezpiecznym W C#!

Jest jeden duży problem w tym kodzie:

S_Initialized nie jest lotny. Oznacza to, że zapis w kodzie inicjalizacyjnym może się poruszać po ustawieniu s_initialized na true, a inne wątki mogą zobaczyć niezainializowany Kod, nawet jeśli s_initialized jest dla nich prawdą. Nie dotyczy to implementacji frameworka firmy Microsoft, ponieważ każdy zapis jest zapisem zmiennym.

Ale również w implementacji Microsoftu odczyt niezainicjalizowanych danych może być ponownie uporządkowany (tj. wstępnie ustawiony przez Procesor), więc jeśli s_initialized jest prawdą, odczyt danych, które powinny być zainicjalizowane, może skutkować odczytaniem starych, niezainicjalizowanych danych z powodu Cache-hits (tj. odczyty są zmieniane).

Na przykład:

Thread 1 reads s_Provider (which is null)  
Thread 2 initializes the data  
Thread 2 sets s\_Initialized to true  
Thread 1 reads s\_Initialized (which is true now)  
Thread 1 uses the previously read Provider and gets a NullReferenceException

Przeniesienie odczytu s_Provider przed odczytem s_Initialized jest całkowicie legalne, ponieważ nigdzie nie ma zmiennego odczytu.

Jeśli s_Initialized będzie volatile, odczyt s_Provider nie będzie mógł się poruszać przed odczytem s_Initialized, a także inicjalizacja dostawcy nie będzie mogła się poruszać po ustawieniu s_initialized na true i wszystko jest teraz w porządku.

Joe Duffy napisał również artykuł o tym problemie: Broken variants on double-checked locking

 34
Author: Thomas Danecker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-28 01:04:11

Poczekaj ... pytanie, które jest w tytule, zdecydowanie nie jest prawdziwym pytaniem, które zadaje Rory.

Tytułowe pytanie ma prostą odpowiedź " Nie " - ale to wcale nie pomaga, gdy widzisz prawdziwe pytanie - na które chyba nikt nie dał prostej odpowiedzi.

Prawdziwe pytanie, które zadaje Rory, jest przedstawione znacznie później i jest bardziej trafne do przykładu, który podaje.

Dlaczego pole s_Initialized jest odczytywane poza zamek?

Odpowiedź na to pytanie jest również prosta, choć zupełnie niezwiązana z atomicznością zmiennego dostępu.

Pole s_Initialized jest odczytywane poza blokadą, ponieważ blokady są drogie.

Ponieważ pole s_Initialized jest zasadniczo "write once", nigdy nie zwróci fałszywie dodatniego wyniku.

Jest to ekonomiczne, aby odczytać go poza zamkiem.

Jest to niski koszt aktywność z wysoki szansa na korzyści.

Dlatego jest odczytywany poza zamkiem - aby uniknąć ponoszenia kosztów używania zamka, chyba że jest wskazany.

Gdyby zamki były tanie, kod byłby prostszy i pomijałby pierwszą kontrolę.

(edit: miła odpowiedź rory. Odczyty logiczne są bardzo atomowe. Gdyby ktoś zbudował procesor z niematomicznymi odczytami boolean, byłby opisywany na DailyWTF.)

 11
Author: Leon Bambrick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-15 14:17:07

Prawidłowa odpowiedź brzmi: "tak, głównie."

  1. odpowiedź Johna odnosząca się do specyfikacji CLI wskazuje, że dostęp do zmiennych nie większych niż 32 bity na 32-bitowym procesorze jest atomowy.
  2. Dalsze potwierdzenie ze specyfikacji C#, sekcja 5.5, Atomiczność odniesień zmiennych :

    Odczyt i zapis następujących typów danych są atomowe: bool, char, byte, sbyte, short, ushort, uint, int, float i typy referencyjne. Ponadto czyta i zapisy typów enum z typem bazowym na poprzedniej liście są również atomowe. Odczyty i zapisy innych typów, w tym long, ulong, double i decimal, jak również typów zdefiniowanych przez użytkownika, nie są gwarantowane jako atomowe.

  3. Kod w moim przykładzie został sparafrazowany z klasy członkowskiej, napisanej przez ASP.NET zespół się, więc zawsze można było założyć, że sposób uzyskiwania dostępu do pola s_Initialized jest poprawny. Teraz wiemy dlaczego.

Edytuj: Jak zauważa Thomas Danecker, mimo że dostęp do pola jest atomowy, s_Initialized naprawdę powinien być oznaczony volatile, aby upewnić się, że blokada nie zostanie przerwana przez procesor zmieniający kolejność odczytów i zapisów.

 7
Author: Rory MacLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 19:32:47

Funkcja Initialize jest nieprawidłowa. Powinno to wyglądać bardziej TAK:

private static void Initialize()
{
    if(s_initialized)
        return;

    lock(s_lock)
    {
        if(s_Initialized)
            return;
        s_Initialized = true;
    }
}

Bez drugiego sprawdzenia wewnątrz zamka możliwe, że kod inicjujący zostanie wykonany dwukrotnie. Tak więc pierwsza kontrola polega na tym, aby wydajność oszczędziła niepotrzebnie robienia blokady, a druga sprawdza przypadek, w którym wątek wykonuje kod inicjujący, ale nie ustawił jeszcze znacznika s_Initialized, a więc drugi wątek przejdzie pierwszą kontrolę i będzie czekał na blokadę.

 2
Author: John Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-07 20:07:38

Odczyty i zapisy zmiennych nie są atomowe. Musisz użyć interfejsów API synchronizacji, aby emulować atomowe odczyty/zapisy.

Aby dowiedzieć się więcej o tym i wielu innych kwestiach związanych z współbieżnością, Pobierz kopię najnowszego spektaklu Joe Duffy ' ego . To Rozpruwacz!

 1
Author: OJ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-13 12:35:12

" czy dostęp do zmiennej w C# jest operacją atomową?"

Nie. I nie chodzi o C#, ani nawet o. net, tylko o procesor.

OJ jest na miejscu, że Joe Duffy jest facetem, aby przejść do tego rodzaju informacji. A "interlocked" to świetny termin wyszukiwania, jeśli chcesz wiedzieć więcej.

"Torn reads" może wystąpić na dowolnej wartości, której pola są większe niż rozmiar wskaźnika.

 1
Author: Leon Bambrick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-15 12:46:02

@Leon
Rozumiem twój punkt widzenia-sposób, w jaki zadałem, a następnie skomentowałem, pytanie pozwala na podjęcie go na kilka różnych sposobów.

Aby było jasne, chciałem wiedzieć, czy jest bezpieczne, aby współbieżne wątki odczytywały i zapisywały do pola logicznego bez wyraźnego kodu synchronizacji, tzn.

Następnie użyłem kodu członkostwa, aby podać konkretny przykład, ale to wprowadziło kilka zakłóceń, podobnie jak blokowanie double-check, fakt, że s_Initialized jest ustawiany tylko raz, i że skomentowałem sam kod inicjalizacyjny.

Mój błąd.

 1
Author: Rory MacLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-15 13:37:48

Można również ozdobić s_Initialized słowem kluczowym volatile i całkowicie zrezygnować z używania lock.

To nie jest poprawne. Nadal napotkasz problem, że drugi wątek przejdzie kontrolę, zanim pierwszy wątek będzie miał szansę ustawić flagę, co spowoduje wielokrotne wykonanie kodu inicjującego.
 1
Author: John Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-07 20:07:47

Myślę, że pytasz, czy s_Initialized może być w niestabilnym stanie, gdy odczyt poza zamkiem. Krótka odpowiedź brzmi: nie. Proste przypisanie / odczyt sprowadzi się do pojedynczej instrukcji montażu, która jest atomowa na każdym procesorze, o którym mogę myśleć.

Nie jestem pewien, jaki jest przypadek przypisania do zmiennych 64 bitowych, to zależy od procesora, zakładam, że nie jest atomowy, ale prawdopodobnie jest na nowoczesnych procesorach 32 bitowych i na pewno na wszystkich procesorach 64 bitowych. Przypisanie kompleksu typy wartości nie będą atomowe.

 1
Author: John Richardson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-07 20:08:17

Myślałem, że były - nie jestem pewien punktu blokady w twoim przykładzie, chyba że jednocześnie robisz coś z s_Provider - wtedy blokada zapewni, że te połączenia będą miały miejsce razem.

Czy to //Perform initialization komentarz pokrywa tworzenie s_provider? Na przykład

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}

W przeciwnym razie ta statyczna właściwość-get i tak zwróci null.

 0
Author: Keith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-13 12:00:51

Być może daje wskazówkę. A poza tym całkiem nieźle.

Domyśliłbym się, że nie są atomowe.

 0
Author: Peteter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-13 12:19:22

Aby Twój kod zawsze działał na słabo uporządkowanych architekturach, musisz umieścić MemoryBarrier przed napisaniem s_Initialized.

s_Provider = new MemershipProvider;

// MUST PUT BARRIER HERE to make sure the memory writes from the assignment
// and the constructor have been wriitten to memory
// BEFORE the write to s_Initialized!
Thread.MemoryBarrier();

// Now that we've guaranteed that the writes above
// will be globally first, set the flag
s_Initialized = true;

Zapis pamięci, który ma miejsce w Konstruktorze MembershipProvider i zapis do s_provider nie jest gwarantowany przed zapisem do s_initialized na słabo uporządkowanym procesorze.

Wiele myśli w tym wątku jest o tym, czy coś jest atomowe, czy nie. Nie o to chodzi. Problemem jest kolejność, w której Twój wątek zapisy są widoczne dla innych wątków . Na słabo uporządkowanych architekturach zapis do pamięci nie występuje w kolejności i to jest prawdziwy problem, a nie to, czy zmienna mieści się w szynie danych.

EDIT: właściwie, mieszam platformy w moich wypowiedziach. W C# Specyfikacja CLR wymaga, aby zapisy były widoczne globalnie, w kolejności (używając drogich instrukcji dla każdego sklepu, jeśli to konieczne). Dlatego nie musisz mieć tej bariery pamięci. Gdyby jednak C lub c++ w przypadku, gdy nie ma takiej gwarancji globalnego porządku widoczności, a twoja docelowa platforma może mieć słabo uporządkowaną pamięć i jest wielowątkowa, musisz upewnić się, że zapisy konstruktorów są globalnie widoczne przed aktualizacją s_initialized, która jest testowana poza blokadą.

 0
Author: doug65536,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-02 10:13:16

An If (itisso) { sprawdzanie logiki jest atomowe, ale nawet jeśli nie było nie ma potrzeby blokowania pierwszej kontroli.

Jeśli jakikolwiek wątek zakończył inicjalizację, to będzie to prawda. Nie ma znaczenia, czy kilka wątków sprawdza się jednocześnie. Wszyscy otrzymają tę samą odpowiedź i nie będzie konfliktu.

Drugie sprawdzenie wewnątrz blokady jest konieczne, ponieważ inny wątek mógł chwycić blokadę jako pierwszy i już zakończył proces inicjalizacji.

 0
Author: James Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-28 01:35:36

To, o co pytasz, to czy dostęp do pola w metodzie jest wielokrotnie atomowy -- na co odpowiedź brzmi nie.

W powyższym przykładzie procedura initialise jest nieprawidłowa, ponieważ może spowodować wielokrotne inicjowanie. Należy sprawdzić flagę s_Initialized wewnątrz zamka, jak również na zewnątrz, aby zapobiec Warunkom wyścigu, w którym wiele wątków odczytuje flagę s_Initialized, zanim któryś z nich faktycznie wykona kod inicjalizacji. Np.,

private static void Initialize()
{
    if (s_Initialized)
        return;

    lock(s_lock)
    {
        if (s_Initialized)
            return;
        s_Provider = new MembershipProvider ( ... )
        s_Initialized = true;
    }
}
 0
Author: olliej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-07 20:09:14

ACK, nevermind... jak wskazano, jest to rzeczywiście błędne. Nie uniemożliwia to drugiemu wątkowi wejścia w sekcję kodu "initialize". Bah.

Można również ozdobić s_Initialized słowem kluczowym volatile i całkowicie zrezygnować z używania lock.

 -1
Author: JoshL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-13 15:21:04