Czy powinienem zadbać o warunki wyścigowe, które prawie na pewno nie mają szans?

52

Rozważmy coś w rodzaju aplikacji GUI, w której główny wątek aktualizuje interfejs użytkownika niemal natychmiast, a jakiś inny wątek odpytuje dane w sieci lub coś, co gwarantuje, że ukończenie zadania zajmie 5–10 sekund.

Otrzymałem na to wiele różnych odpowiedzi, ale niektórzy twierdzą, że jeśli jest to wyścigowy warunek statystycznej niemożliwości, nie przejmuj się tym wcale, ale inni powiedzieli, że jeśli jest nawet 10-53 % (żartuję nie na liczbach, oto, co słyszałem) o niektórych magiach voodoo, które dzieją się ze względu na rasę, zawsze uzyskuj / zwalniaj blokady w wątku, który tego potrzebuje.

Jakie są Twoje myśli? Czy dobrą praktyką programistyczną jest radzenie sobie z warunkami wyścigu w takich statystycznie niemożliwych sytuacjach? czy też dodanie dodatkowych linii kodu w celu ograniczenia czytelności byłoby całkowicie niepotrzebne, a nawet przyniosło skutek przeciwny do zamierzonego?

l46kok
źródło
21
Kiedy ludzie określają takie szanse, dlaczego nikt nie pyta o wykształcenie osoby, która podaje ten numer? Potrzebujesz formalnego wykształcenia w statystyce, aby móc poprzeć taką liczbą.
Pieter B,
27
Jako fizyk p <1E-140 oznacza p = 0. Nie wydarzy się w tym wszechświecie. 0,00000000000000000000000000000000000000000000000000001% jest znacznie większy.
MSalters
15
Upewnij się, że ten stan wyścigu nie może doprowadzić do tego, że ktoś chętnie zawiesi Twoją aplikację. Może to być przyczyną problemu z bezpieczeństwem.
toasted_flakes
27
Jedna na milion szans zdarza się dziewięć razy na dziesięć.
Kaz Dragon
27
„prawie na pewno nie ma szans na wystąpienie?” oznacza, że ​​dzieje się to w produkcji o 3 nad ranem i najprawdopodobniej będzie bardzo kosztowne.

Odpowiedzi:

137

Jeśli jest to zdarzenie 1 na 10 ^ 55, nie ma potrzeby kodowania tego zdarzenia. Oznaczałoby to, że gdybyś wykonał operację 1 milion razy na sekundę, dostawałbyś jeden błąd co 3 * 10 ^ 41 lat, czyli mniej więcej 10 ^ 31 razy wiek wszechświata. Jeśli Twoja aplikacja ma błąd tylko raz na każde bilion bilionów miliardów wieków wszechświata, to prawdopodobnie jest to wystarczająco wiarygodne.

Jednak postawiłbym bardzo mocno, że błąd nie jest bliski tak mało prawdopodobnego. Jeśli potrafisz wyobrazić sobie błąd, jest prawie pewne, że wystąpi on co najmniej od czasu do czasu, dlatego warto na początku poprawnie go zakodować. Ponadto, jeśli kodujesz wątki poprawnie na początku, aby odpowiednio uzyskać i zwolnić blokady, kod będzie znacznie łatwiejszy do utrzymania w przyszłości. Kiedy robisz zmianę, nie musisz się martwić, że musisz ponownie przeanalizować wszystkie potencjalne warunki wyścigu, ponownie obliczyć ich prawdopodobieństwa i upewnić się, że się nie powtórzą.

Justin Cave
źródło
66
Przypomina mi się komentarz, który czytałem lata temu, ale nie mogę teraz znaleźć: „1 na milion szans to zwykle następny wtorek”. +1 za powiedzenie, że „nigdzie w pobliżu to mało prawdopodobne”.
Bevan
2
+1 dla zakładu. Najlepszym sposobem radzenia sobie z warunkami wyścigowymi jest pozbycie się ich.
Blrfl,
10
@Bevan „Szansa 1 na milion przypada zwykle na następny wtorek” ... chyba że grasz na loterii :)
dasblinkenlight
22
@dasblinkenlight Ale szanse na kogoś wygranej w większości loterii osiągnie 100%. Przewidywanie, kto teraz jest wyzwaniem.
Bevan
3
@Bevan: Ten komentarz był dokładnie tym, co przyszło mi do głowy, gdy czytam pytanie - oto odniesienie: blogs.msdn.com/b/larryosterman/archive/2004/03/30/104165.aspx
Doc Brown,
69

Z punktu widzenia kosztów i korzyści powinieneś napisać dodatkowy kod tylko wtedy, gdy uzyska wystarczającą korzyść.

Na przykład, jeśli najgorszą rzeczą, która miałaby miejsce, gdyby niewłaściwy wątek „wygrał wyścig”, jest to, że informacja nie byłaby wyświetlana, a użytkownik musiałby kliknąć „odśwież”, nie zawracaj sobie głowy chronieniem się przed wyścigiem: pisać dużo kodu nie warto naprawiać czegoś tak nieznaczącego.

Z drugiej strony, jeśli warunek wyścigu może spowodować nieprawidłowe przelewy pieniężne między kontami bankowymi, musisz chronić się przed warunkiem wyścigu bez względu na to, ile kodu musisz napisać, aby rozwiązać ten problem.

dasblinkenlight
źródło
20
+1: za rozróżnienie między „porażką, która wygląda jak porażka” i „porażką, która wygląda jak sukces”. Niepoprawne informacje są znacznie poważniejsze, w zależności od domeny.
deworde
2
+1 daje dużą różnicę w wynikach wyścigu.
Grant
+1 Konsekwencje warunków wyścigu powinny być głównym czynnikiem decydującym o tym, czy należy je rozwiązać. Warunki wyścigu, które mogą spowodować awarię samolotu, znacznie różnią się od warunków, które mogą zmusić użytkownika do ponownego otwarcia aplikacji.
poke
1
+1: Powiedziałbym, że konsekwencje są prawdopodobnie tym, co powinieneś analizować, a nie prawdopodobieństwo ich wystąpienia. Jeśli konsekwencje nie mają znaczenia, być może nie będziesz musiał radzić sobie z wyścigiem NAWET, jeśli jest to bardzo powszechne.
Lew
1
Ale nie zakładaj, że automatyczne ustawienie wyścigu oznacza, że ​​musisz napisać więcej kodu. Równie dobrze może to oznaczać usunięcie dużej części błędnego kodu i zastąpienie go mniejszą częścią poprawnego kodu.
JesperE,
45

Znalezienie warunków wyścigu jest trudną częścią. Prawdopodobnie poświęciłeś prawie tyle samo czasu na napisanie tego pytania, ile zajęłoby Ci jego naprawienie. To nie tak, że czyni go o wiele mniej czytelnym. Programiści oczekują kodu synchronizacji w takich sytuacjach i mogą tracić więcej czasu, zastanawiając się, dlaczego go nie ma i czy dodanie go naprawi ich niezwiązany błąd.

Jeśli chodzi o prawdopodobieństwa, byłbyś zaskoczony. W zeszłym roku miałem raport o błędzie dotyczący stanu wyścigu, którego nie mogłem odtworzyć dzięki tysiącom zautomatyzowanych prób, ale jeden system jednego klienta widział go cały czas. Wartość biznesowa spędzenia 5 minut na naprawie teraz, w porównaniu z ewentualnym rozwiązaniem problemu „niemożliwego” błędu w instalacji klienta, sprawia, że ​​wybór nie jest trudny.

Karl Bielefeldt
źródło
1
To też! Unikaj, aby inni programiści zastanawiali się nad możliwymi problemami podczas odczytywania twojego kodu, robiąc to, co jest konieczne (nawet jeśli „mało prawdopodobne” jest niepowodzenie).
Casey Kuball,
Twój punkt jest dobrze przemyślany (poprawki dokonane teraz są szybsze i tańsze niż poprawki dokonane później), z wyjątkiem tego, że nigdy nie będzie tylko „5 minut, aby to naprawić”.
iconoclast
2
+1 za wskazanie, że prawdopodobieństwo wystąpienia wyścigu prawdopodobnie zależy od wielu czynników, więc nawet jeśli wygląda to mało prawdopodobne w twojej konfiguracji, może się to zdarzać częściej w systemie klienta / na innym systemie operacyjnym / w następnej wersji itp.
Śleske,
27

Uzyskaj i zwolnij blokady. Prawdopodobieństwa się zmieniają, algorytmy się zmieniają. To zły nawyk, a kiedy coś pójdzie nie tak, nie musisz się zatrzymywać i zastanawiać, czy źle trafiłeś ...

jmoreno
źródło
6
+1 za zmianę algorytmów. W tej chwili, kiedy zdajesz sobie sprawę z warunków wyścigu, prawdopodobieństwo jest niskie. Po roku, gdy zapomnisz o stanie wyścigu, możesz wprowadzić zmiany w kodzie, co znacznie zmienia czas i prawdopodobieństwo wystąpienia błędu.
Phil
13

a jakiś inny wątek odpytuje dane w sieci lub coś, co gwarantuje, że wykonanie zadania zajmie 5–10 sekund.

Dopóki ktoś nie wprowadzi warstwy buforowania w celu poprawy wydajności. Nagle inny bieżnik zakończył się niemal natychmiast, a stan wyścigu objawia się częściej niż nie.

Gdyby dokładnie tak się stało kilka tygodni temu, znalezienie błędu zajęło około 2 pełnych dni programisty.

Zawsze ustalaj warunki wyścigu, jeśli je rozpoznasz.

Michael Borgwardt
źródło
8

Prosty kontra poprawny.

W wielu przypadkach prostota przebija poprawność. To problem z kosztami.

Ponadto warunki wyścigowe to paskudne rzeczy, które zwykle nie przestrzegają prostych statystyk. Wszystko idzie dobrze, dopóki jakaś inna pozornie niezwiązana synchronizacja nie spowoduje, że stan twojej rasy nagle się wydarzy w połowie czasu. O ile nie włączysz dzienników lub nie zdebugujesz kodu.

Pragmatyczną alternatywą dla zapobiegania warunkom wyścigowym (co może być trudne) może być ich wykrycie i zarejestrowanie (premia za ciężkie i wczesne porażki). Jeśli to się nigdy nie zdarzy, niewiele straciłeś. Jeśli tak się stanie, masz solidne uzasadnienie, aby spędzić dodatkowy czas na jego naprawianiu.

ptyx
źródło
1
+1 za logowanie i nie powiedzie się wcześniej, jeśli naprawienie go jest zbyt skomplikowane.
Martin Ba,
W wielu przypadkach prostota przebija kompletność. Synchronizacja prawie nigdy nie występuje w tych przypadkach. Prawie zawsze wróci, by cię ugryźć (lub biedaka, którego zadaniem jest utrzymanie twojego kodu) później.
reirab
@reirab Nie zgadzam się. Jeśli weźmiesz pod uwagę rzadkie zdarzenia, zarejestrowana awaria jest opłacalna. Przykład: jeśli Twoja aplikacja na telefon ma wskaźnik awaryjności 1/100 (awaria), jeśli użytkownik przełącza sieć z dokładnym przejściem miesiąca (1/31 23:59:00 -> 2/1 00:00:00), możesz Prawdopodobnie nigdy o tym nie usłyszę. Ale wtedy szansa na awarię 1/10 ^ 9 przy połączeniu na serwerze jest niedopuszczalna. To zależy.
ptyx
7

Jeśli twoje warunki rasowe są związane z bezpieczeństwem, zawsze powinieneś kodować, aby temu zapobiec.

Typowym przykładem są warunki wyścigu z tworzeniem / otwieraniem plików w unixie, które w niektórych okolicznościach mogą prowadzić do ataków eskalacji uprawnień, jeśli program z warunkiem wyścigu działa z wyższymi uprawnieniami niż użytkownik wchodzący w interakcję z nim, na przykład proces demona systemu lub co gorsza, jądro.

Nawet jeśli warunek wyścigu ma szansę na losowo 10 ^ (- 80) , być może zdeterminowany atakujący ma przyzwoitą szansę na stworzenie takich warunków celowo i sztucznie.

Bristol
źródło
6

Therac-25!

Deweloperzy projektu Therac-25 byli dość pewni co do czasu między interfejsem użytkownika a problemem związanym z interfejsem w terapeutycznej maszynie XRAY.

Nie powinny.

Możesz dowiedzieć się więcej o tej słynnej katastrofie oprogramowania na śmierć i życie pod adresem:

http://www.youtube.com/watch?v=izGSOsAGIVQ

lub

http://en.wikipedia.org/wiki/Therac-25

Twoja aplikacja może być znacznie mniej wrażliwa na awarie niż urządzenia medyczne. Pomocną metodą jest ocena narażenia na ryzyko jako iloczynu prawdopodobieństwa wystąpienia i kosztu wystąpienia w całym okresie użytkowania produktu dla wszystkich jednostek, które można wyprodukować.

Jeśli zdecydowałeś się zbudować swój kod, aby był trwały (i wygląda na to, że masz), powinieneś wziąć pod uwagę prawo Moore'a, które może z łatwością wykasować kilka zer co kilka lat, gdy komputery wewnątrz lub na zewnątrz systemu stają się szybsze. Jeśli wyślesz tysiące kopii, odetnij więcej zer. Jeśli użytkownicy wykonują tę operację codziennie (lub co miesiąc) przez lata, zabierz jeszcze kilka. Jeśli jest używany, gdy dostępne jest włókno Google, co wtedy? Jeśli śmieci interfejsu użytkownika zbierają operacje w trakcie GUI, czy to wpływa na wyścig? Czy korzystasz z biblioteki Open Source lub Windows za GUI? Czy aktualizacje tam mogą wpływać na czas?

Semafory, zamki, muteksy, synchronizacja barier to jedne ze sposobów synchronizacji działań między wątkami. Potencjalnie, jeśli ich nie używasz, inna osoba, która utrzymuje Twój program, może, a następnie dość szybko założyć, że relacje między wątkami mogą się zmienić, a obliczenia dotyczące warunków wyścigu mogą zostać unieważnione.

Zalecam, abyś wyraźnie zsynchronizował, ponieważ chociaż nigdy nie widzisz, że stwarza to problem, klient może. Ponadto, nawet jeśli warunki wyścigu nigdy nie wystąpią, co zrobić, jeśli ty lub Twoja organizacja zostaną wezwani do sądu w celu obrony twojego kodu (ponieważ Toyota była związana z Priusem kilka lat temu). Im dokładniejsza jest twoja metodologia, tym lepiej ci się spodoba. Lepiej byłoby powiedzieć „chronimy się przed takim mało prawdopodobnym przypadkiem jak ten ...” niż powiedzieć „wiemy, że nasz kod zawiedzie, ale spisaliśmy to równanie, aby pokazać, że nie wydarzy się za naszego życia. Prawdopodobnie. „

Wygląda na to, że obliczenie prawdopodobieństwa pochodzi od kogoś innego. Czy znają Twój kod i znasz je na tyle, aby mieć pewność, że nie popełniono błędu? Jeśli policzyłem dla czegoś 99,99997% wiarygodności, mógłbym również wrócić do moich klas statystyki college'u i pamiętać, że nie zawsze otrzymywałem 100% i wycofałem całkiem sporo procent z moich osobistych szacunków wiarygodności.

DeveloperDon
źródło
1
+1 za wzmiankę o Therac-25. Wiele ważnych lekcji tutaj.
Stuart Marks
Chociaż uważam, że to dobra odpowiedź, możesz argumentować, że twój hobby GUI z pewnością nie spowoduje śmierci ludzi, jeśli nie wyeliminujesz wyścigu.
marktani
Nie jestem zwolennikiem kłótni, ale gdybym tak był, mógłbym argumentować, że za każdym razem, gdy piszemy kod, powinniśmy pisać go poprawnie. Jeśli możemy przećwiczyć wyciąganie warunków wyścigowych z naszych projektów hobby, w których kod jest prostszy i być może jesteśmy jedynym autorem, będziemy o wiele bardziej przygotowani, gdy zajmiemy się projektami roboczymi, w których praca kilku autorów musi być zintegrowana.
DeveloperDon
4

czy dodanie większej liczby wierszy kodu w celu ograniczenia czytelności byłoby całkowicie niepotrzebne, a nawet przyniosło efekt przeciwny do zamierzonego?

Prostota jest dobra tylko wtedy, gdy jest również poprawna. Ponieważ ten kod nie jest prawidłowy, przyszli programiści będą nieuchronnie spojrzeć na to kiedy szuka powiązanego błędu.

Niezależnie od tego, jak sobie z tym poradzisz (poprzez zalogowanie, udokumentowanie lub dodanie blokad - zależy to od kosztu), zaoszczędzisz innym programistom czas patrząc na kod.

Casey Kuball
źródło
3

Zależy to od kontekstu. Jeśli jest to zwykła gra na iPhone'a, prawdopodobnie nie. Prawdopodobnie system sterowania lotem do następnego załogowego pojazdu kosmicznego. Wszystko zależy od tego, jakie będą konsekwencje, jeśli zdarzy się „zły” wynik mierzony szacunkowym kosztem jego naprawienia.

Rzadko istnieje odpowiedź „jeden rozmiar dla wszystkich” na tego typu pytania, ponieważ nie są to pytania programistyczne, lecz pytania ekonomiczne.

Grandmaster B.
źródło
3
„System sterowania lotem dla następnego załogowego pojazdu kosmicznego” OSTATECZNIE .
deworde
prawdopodobnie ... zdecydowanie ... to zależy od tego, kto był w rakiecie :-)
GrandmasterB
3

Tak, oczekuj nieoczekiwanego. Spędziłem godziny (w kodzie innych osób ^^) na śledzeniu warunków, które nigdy nie powinny się zdarzyć.

Rzeczy takie jak zawsze mają inny, zawsze mają domyślną wielkość liter, inicjują zmienne (tak, naprawdę .. z tego wynikają błędy), sprawdzają pętle pod kątem zmiennych używanych dla każdej iteracji itp.

Jeśli martwisz się problemami związanymi z wątkami, czytaj blogi, artykuły i książki na ten temat. Obecny temat wydaje się niezmiennymi danymi.

Paweł
źródło
3

Po prostu to napraw.

Widziałem dokładnie to. Jeden wątek wysyła żądanie sieciowe do serwera, który wyszukuje złożone bazy danych i odpowiada, zanim drugi wątek przejdzie do następnego wiersza kodu. Zdarza się.

Jakiś klient gdzieś zdecyduje, że pewnego dnia uruchomi coś, co pochłonie cały procesor dla „szybkiego” wątku, pozostawiając wolny wątek działający, i będzie ci przykro :)

JohnB
źródło
1

Jeśli rozpoznałeś mało prawdopodobny stan wyścigu, przynajmniej udokumentuj go w kodzie!

EDYCJA: Powinienem dodać, że naprawię to, jeśli to w ogóle możliwe, ale w momencie pisania powyższego żadna inna odpowiedź nie powiedziała wprost, że przynajmniej dokumentuje problem w kodzie.

Mark Hurd
źródło
1
Tak, a przynajmniej spróbuj go wykryć i zarejestruj, jeśli tak się stanie. IMHO jest całkowicie w porządku, aby nie uniknąć każdego błędu. Ale przynajmniej powiedz komuś, że to się wydarzyło i że twoje przypuszczenie, że to nie będzie błędne.
Steve Bennett,
0

Myślę, że jeśli już wiesz, jak i dlaczego tak się stało, równie dobrze sobie z tym poradzi. To znaczy, jeśli nie zajmuje dużo zasobów.

Sjaak van der Heide
źródło
0

Wszystko zależy od konsekwencji warunków wyścigu. Myślę, że ludzie odpowiadający na twoje pytanie są poprawni w swojej branży. Mój jest silnik konfiguracji routera. Dla mnie warunki wyścigowe powodują, że systemy stoją w miejscu, są uszkodzone lub nieskonfigurowane, nawet jeśli mówi się, że się udało. Zawsze używam semaforów na router, aby nie musiałem niczego ręcznie czyścić.

Myślę, że część mojego kodu GUI nadal jest podatna na warunki wyścigu w taki sposób, że użytkownik może otrzymać błąd, ponieważ wystąpił warunek wyścigu, ale nie miałbym takich możliwości, gdyby istniała szansa na uszkodzenie danych lub niewłaściwe zachowanie podanie po takim zdarzeniu.

Sylwester
źródło
0

Co zabawne, ostatnio spotkałem ten problem. Nawet nie zdawałem sobie sprawy, że wyścig jest możliwy w moich okolicznościach. Wyścig pojawił się dopiero, gdy procesory wielordzeniowe stały się normą.

Scenariusz był mniej więcej taki. Sterownik urządzenia zgłosił zdarzenia do obsługi przez oprogramowanie. Kontrola musiała jak najszybciej wrócić do sterownika urządzenia, aby zapobiec przekroczeniu limitu czasu w urządzeniu. Aby to zapewnić, zdarzenie zostało zarejestrowane i umieszczone w kolejce w osobnym wątku.

Receive event from device:
{
    Record event details.
    Enqueue event in the queuing thread.
    Acknowledge the event.
}

Queueing thread receives an event:
{
    Retrieve event details.
    Process event.
    Send next command to device.
}

To działało dobrze przez lata. Wtedy nagle zawiodłby w niektórych konfiguracjach. Okazuje się, że wątek kolejkowania działał teraz naprawdę równolegle do wątku obsługi zdarzeń, zamiast współdzielić czas jednego procesora. Udało się wysłać następne polecenie do urządzenia przed potwierdzeniem zdarzenia, co powoduje błąd poza sekwencją.

Biorąc pod uwagę, że wpłynęło to tylko na jednego klienta w jednej konfiguracji, ze wstydem postawiłem Thread.Sleep(1000)problem. Od tamtej pory nie było problemu.

Hand-E-Food
źródło