Pytanie Czy kontrole są bezpieczne dla wątków zerowych?


Mam kod, w którym wyjątki są zgłaszane na nowy wątek, który muszę uznać i zajmować się na głównym wątku. Aby to osiągnąć, dzielę stan między wątkami, używając pola, które przechowuje wyjątek.

Moje pytanie brzmi czy muszę używać blokad przy sprawdzaniu zerowej wartości jak robię w poniższym przykładzie kodu?

public class MyClass
{
    readonly object _exceptionLock = new object();
    Exception _exception;

    public MyClass()
    {
        Task.Run(() =>
        {
            while (CheckIsExceptionNull())
            {
                // This conditional will return true if 'something has gone wrong'.
                if(CheckIfMyCodeHasGoneWrong())
                {
                    lock(_exceptionLock)
                    {
                        _exception = new GoneWrongException();
                    }
                }
            }
        });
    }

    bool CheckIsExceptionNull() // Is this method actually necessary?
    {
        lock (_exceptionLock)
        {
            return _exception == null;
        }
    }

    // This method gets fired periodically on the Main Thread.
    void RethrowExceptionsOnMainThread()
    {
        if (!CheckIsExceptionNull())
        {
            lock (_exceptionLock)
            {
                throw _exception; // Does this throw need to be in a lock?
            }
        }
    }
}

Do tego, czy muszę używać blokady podczas rzucania wyjątku na główny wątek?


11
2018-06-15 10:56


pochodzenie


Za każdym razem pytanie "czy potrzebuję użyć zamka", jest podejrzane. Bezsporna blokada trwa kilkanaście nanosekund. Czy Twoje pytanie: "mój program ma już gwarancję poprawności, ale jest o kilkanaście nanosekund zbyt wolny, ten problem z wydajnością można przypisać do blokady, i wiem, że w rezultacie nie odniesie sukcesu na rynku, więc mogę usunąć ten zamek i zachować poprawność mojego programu? ". Twój kod nie jest nawet poprawny na początku, a ja bardzo wątpię, że nanosekundy spędzone w bezspornym zamyśle spowodują, że twój program będzie zbyt wolny. - Eric Lippert
Lepsze pytanie brzmi: "w jaki sposób mogę wyeliminować użycie jawnego zarządzania wątkami i pamięci współdzielonej za pomocą biblioteki równoległej zadań, aby wyrazić pojęcia sukcesu i niepowodzenia asynchronicznie wykonywanego zadania?". Obiekt Task ma już właściwość, że wyjątek wygenerowany podczas wykonywania zadania może być bezpiecznie buforowany i ponownie zgłaszany w kontekście wykonywania, który zarządza zadaniem. - Eric Lippert


Odpowiedzi:


Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że twój kod nie jest bezpieczny dla wątków, ponieważ masz wątek wyścigowy: sprawdzasz CheckIsExceptionNull w innym zablokowanym regionie, do którego rzucasz, ale: wartość może się zmieniać między testem a rzutem.

Dostęp do pola nie gwarantuje bezpieczeństwa w wątku. W szczególności, podczas gdy odniesienia nie może  być rozdarty (tyle jest gwarantowane - czyta i pisze odniesienia są atomowe), to jest nie gwarantowane, że różne wątki będą widzieć najnowsze wartości, z powodu buforowania procesora itp. Jest bardzo mało prawdopodobne, aby cię ugryźć, ale to jest problem z problemami z gwintowaniem w ogólnym przypadku; p

Osobiście prawdopodobnie sprawiłbym, że pole jest niestabilne i użyję lokalnego. Na przykład:

var tmp = _exception;
if(tmp != null) throw tmp;

Powyższe nie ma wyścigu wątku. Dodawanie:

volatile Exception _exception;

zapewnia, że ​​wartość nie jest buforowana w rejestrze (chociaż jest to technicznie efekt uboczny, a nie zamierzony / udokumentowany efekt volatile)


9
2018-06-15 11:04



"Szczerze mówiąc, zniechęcam cię do tworzenia niestabilnych pól", Eric Lippert: (stackoverflow.com/a/17530556/7122). Proszę nie zachęcaj do jego używania. - David Arno
@DavidArno Nie jestem pewien, że dodanie lock, Thread.VolatileRead albo kilka Interlocked poprawi czytelność, jednak ... - Marc Gravell♦
@DavidArno Nie ma nic złego w używaniu niestabilności, jeśli wiesz, co robisz, uważam, że nie jest to częste dla osób, które zadają pytania na temat SO. - Yuval Itzchakov
@MarcGravell Nie ma wątpliwości, że ta droga może być wątpliwa, szczególnie gdy używasz efekt uboczny, i na pewno za pomocą lock bardzo by go uratowało przed popełnianiem błędów na drodze, ale to nie znaczy, że nie jest to poprawna odpowiedź. - Yuval Itzchakov
Tak naprawdę to myślę Volatile.Read() jest lepsze niż tworzenie zmiennej volatile, ponieważ jest lepiej ukierunkowany (robisz to tylko tam, gdzie go potrzebujesz) i sprawia, że ​​staje się bardziej oczywistym, że robi się lotny odczyt. (Zauważ, że Volatile.Read() NIE jest taki sam jak Thread.VolatileRead() który nie powinny być używane.) - Matthew Watson


Może być wiele interesujących i przydatnych informacji na temat takich pytań. Możesz spędzać godziny na czytaniu blogów i książek na ten temat. Nawet ludzie, którzy faktycznie to wiedzą, nie zgadzają się. (Jak widać w poście od Marc Gravell i Eric Lippert).

Więc nie ma odpowiedzi na to pytanie ode mnie. Tylko sugestia: Zawsze używaj zamków. Gdy tylko więcej niż jeden wątek uzyskuje dostęp do pola. Bez ryzyka, bez zgadywania, bez dyskusji na temat technologii kompilatora i procesora. Po prostu używaj zamków i zachowaj je, aby działało nie tylko w większości przypadków na twoim komputerze, ale w każdym przypadku na każdej maszynie.

Dalsze czytanie:


0
2018-06-15 13:55



Pomijając "zawsze używaj zamków" - coraz częściej używam Interlocked koniec lock - ale mam skłonność do robienia całkiem sporych systemów. Chodzi mi jednak o to: Interlocked jest również "bez ryzyka, bez zgadywania, bez technologii procesora" itp - Marc Gravell♦
Tak, prawdopodobnie. Nie widziałem wielu przypadków, w których Interlocked jest rzeczywiście użyteczny, ponieważ jest bardzo ograniczony. Chyba jest taki obszar, w którym idealnie pasuje. - Stefan Steinegger