Czego szukasz podczas debugowania zakleszczeń?

25

Ostatnio pracuję nad projektami, które intensywnie wykorzystują wątki. Myślę, że jestem w porządku przy ich projektowaniu; w jak największym stopniu korzystaj z projektowania bezstanowego, blokuj dostęp do wszystkich zasobów, których potrzebuje więcej niż jeden wątek itp. Moje doświadczenie w programowaniu funkcjonalnym bardzo to pomogło.

Jednak czytając kod wątku innych osób, jestem zdezorientowany. W tej chwili debuguję impas, a ponieważ styl kodowania i projekt różnią się od mojego osobistego stylu, trudno mi dostrzec potencjalne warunki impasu.

Czego szukasz podczas debugowania zakleszczeń?

Michael K.
źródło
Pytam o to zamiast SO, ponieważ chcę bardziej ogólnych wskazówek dotyczących debugowania zakleszczeń, a nie konkretnej odpowiedzi na mój problem.
Michael K
Strategie, o których myślę, rejestrują się (jak wskazało kilka innych), analizując wykres impasu, kto czeka na blokadę, która jest utrzymywana przez kogo (patrz stackoverflow.com/questions/3483094/ ... wskaźniki) i blokuj adnotacje (patrz clang.llvm.org/docs/ThreadSafetyAnalysis.html ). Nawet jeśli nie jest to twój kod, możesz spróbować przekonać autora do dodania adnotacji - prawdopodobnie znajdą one błędy i naprawią je (prawdopodobnie włączając twoje) w tym procesie.
Don Hatch

Odpowiedzi:

23

Jeśli sytuacja jest prawdziwym impasem (tj. Dwa wątki utrzymują dwa różne zamki, ale co najmniej jeden wątek chce blokady, który utrzymuje drugi wątek), musisz najpierw porzucić wszystkie wstępne koncepcje dotyczące sposobu blokowania wątków. Nie zakładaj niczego. Możesz usunąć wszystkie komentarze z kodu, który oglądasz, ponieważ komentarze te mogą sprawić, że uwierzysz w coś, co nie jest prawdą. Trudno to wystarczająco podkreślić: nie zakładaj niczego.

Następnie określ, które blokady będą blokowane, gdy wątek będzie próbował zablokować coś innego. Jeśli możesz, upewnij się, że nić odblokowuje się w odwrotnej kolejności od blokady. Co więcej, upewnij się, że nić posiada tylko jeden zamek na raz.

Starannie przeanalizuj wykonanie wątku i sprawdź wszystkie zdarzenia blokujące. Przy każdej blokadzie ustal, czy wątek zawiera inne blokady, a jeśli tak, to w jakich okolicznościach inny wątek, wykonujący podobną ścieżkę wykonania, może dostać się do rozpatrywanego zdarzenia blokowania.

Z pewnością możliwe jest, że nie znajdziesz problemu, zanim skończy Ci się czas lub pieniądze.

Bruce Ediger
źródło
4
+1 Wow, to pesymistyczne ... ale to nie prawda. Jest rzeczą oczywistą, że nie można znaleźć wszystkich błędów. Dzięki za sugestie!
Michael K
Bruce, twoja charakterystyka „prawdziwego impasu” jest dla mnie zaskakująca. Myślałem, że impasem między dwoma wątkami jest, gdy każdy czeka na zamek, który trzyma drugi. Wydaje się, że twoja definicja obejmuje również przypadek, w którym nić, przytrzymując jeden zamek, czeka na uzyskanie drugiego zamka, który jest obecnie utrzymywany przez inny wątek. To nie brzmi dla mnie jak impas; czy to jest ??
Don Hatch
@DonHatch - źle sformułowałem. Opisana sytuacja nie jest impasem. Miałem nadzieję przekazać bałagan związany z debugowaniem sytuacji obejmującej blokadę przytrzymującą wątek A, a następnie próbującą uzyskać blokadę B, podczas gdy wątek trzymający blokadę B próbuje uzyskać blokadę A. Może. A może sytuacja jest o wiele bardziej skomplikowana. Musisz tylko zachować otwarty umysł na temat kolejności uzyskiwania blokady. Sprawdź wszystkie założenia. Nic nie ufaj.
Bruce Ediger
+1 sugeruje uważne przeczytanie kodu i sprawdzenie wszystkich operacji blokady w izolacji. Znacznie łatwiej jest spojrzeć na złożony wykres, uważnie badając pojedynczy węzeł, niż próbować zobaczyć całość naraz. Ile razy znalazłem problem, po prostu patrząc na kod i uruchamiając różne scenariusze w mojej głowie.
Newtopian
11
  1. Jak powiedzieli inni ... jeśli możesz uzyskać przydatne informacje do logowania, spróbuj najpierw, ponieważ jest to najłatwiejsza rzecz do zrobienia.

  2. Zidentyfikuj zaangażowane blokady. Zmień wszystkie muteksy / semafory, które czekają wiecznie, na czas czeka ... coś śmiesznie długiego jak 5 minut. Zaloguj błąd, gdy upłynie limit czasu. To przynajmniej skieruje Cię w stronę jednej z blokad, które są zaangażowane w problem. W zależności od zmienności czasu możesz mieć szczęście i znaleźć obie blokady po kilku biegach. Użyj kodu / warunków błędu funkcji, aby zarejestrować ślad pseudo stosu po tym, jak czas oczekiwania nie określi, w jaki sposób się tam dostałeś. Powinno to pomóc zidentyfikować wątek związany z problemem.

  3. Inną rzeczą, którą możesz wypróbować, jest zbudowanie biblioteki opakowań wokół usług mutex / semaforów. Śledź, jakie wątki mają każdy muteks i jakie wątki czekają na muteks. Utwórz wątek monitorujący, który sprawdza, jak długo wątki blokowały. Uruchom na określony czas i zrzuć śledzone informacje o stanie.

W pewnym momencie konieczna będzie zwykła inspekcja starego kodu.

Pemda
źródło
6

Pierwszym krokiem (jak mówi Péter) jest logowanie. Chociaż z mojego doświadczenia jest to często problematyczne. W przypadku ciężkiego przetwarzania równoległego często nie jest to możliwe. Musiałem raz debugować coś podobnego z siecią neuronową, która przetwarzała 100 000 węzłów na sekundę. Błąd pojawił się dopiero po kilku godzinach, a nawet jedna linia wyjściowa spowolniła tak bardzo, że zajęłoby to kilka dni. Jeśli rejestrowanie jest możliwe, skoncentruj się mniej na danych, a bardziej na przebiegu programu, dopóki nie dowiesz się, w której części to się dzieje. Po prostu prosta linia na początku każdej funkcji i jeśli możesz znaleźć odpowiednią funkcję, podziel ją na mniejsze części.

Inną opcją jest usunięcie części kodu i danych w celu zlokalizowania błędu. Może nawet napiszesz mały program, który bierze tylko niektóre klasy i uruchamia tylko najbardziej podstawowe testy (oczywiście w kilku wątkach). Usuń wszystko związane z GUI, na przykład wszelkie dane wyjściowe dotyczące aktualnego stanu przetwarzania. (Często stwierdziłem, że interfejs użytkownika jest źródłem błędu)

W swoim kodzie staraj się postępować zgodnie z pełnym logicznym przepływem kontroli między inicjowaniem blokady a jej zwolnieniem. Częstym błędem może być blokowanie na początku funkcji, odblokowywanie na końcu, ale gdzieś pomiędzy nimi znajduje się warunkowa instrukcja return. Wyjątki mogą również uniemożliwić zwolnienie.

Thorsten Müller
źródło
„Wyjątki mogą zapobiec wydaniu” -> Szkoda mi języków, które nie mają zmiennych o zasięgu: /
Matthieu M.
1
@ Matthieu: Posiadanie zmiennych o zasięgu i właściwe ich używanie może być dwiema różnymi rzeczami. I zapytał ogólnie o możliwe problemy, nie wspominając o konkretnym języku. Jest to więc jedna rzecz, która może wpłynąć na przepływ kontroli.
thorsten müller
3

Moi najlepsi przyjaciele otrzymali wyciągi / dzienniki w interesujących miejscach w kodzie. Zwykle pomagają mi lepiej zrozumieć, co naprawdę dzieje się w aplikacji, bez zakłócania synchronizacji między różnymi wątkami, co może uniemożliwić odtworzenie błędu.

Jeśli to się nie powiedzie, moją jedyną pozostałą metodą jest wpatrywanie się w kod i próba zbudowania modelu mentalnego różnych wątków i interakcji oraz próba wymyślenia możliwych szalonych sposobów na osiągnięcie tego, co najwyraźniej się wydarzyło :-) Ale ja nie uważam się za bardzo doświadczonego pogromcę impasu. Mam nadzieję, że inni będą mogli podać lepsze pomysły, z których również mogę się nauczyć :-)

Péter Török
źródło
1
Debugowałem dzisiaj kilka takich martwych zamków. Sztuką było owinięcie pthread_mutex_lock () makrem, które wypisuje funkcję, numer linii, nazwę pliku i nazwę zmiennej mutex (tokenizując ją) przed i po uzyskaniu blokady. Zrób to samo dla pthread_mutex_unlock (). Kiedy zobaczyłem, że mój wątek się zawiesił, musiałem tylko spojrzeć na dwie ostatnie wiadomości, były dwa wątki, które próbowały się zablokować, ale nigdy ich nie kończyły! Teraz pozostaje tylko dodać mechanizm przełączający to w czasie wykonywania. :-)
Plumenator
3

Przede wszystkim postaraj się uzyskać autora tego kodu. Prawdopodobnie będzie miał pomysł, co napisał. nawet jeśli dwoje nie potraficie wskazać problemu tylko przez rozmowę, przynajmniej możecie usiąść z nim, aby wskazać część impasu, która będzie znacznie szybsza niż zrozumienie jego / jej kodu bez pomocy.

W przeciwnym razie, jak powiedział Péter Török, rejestrowanie jest prawdopodobnie dobrym rozwiązaniem. O ile mi wiadomo, debugger źle wykonał pracę w środowisku wielowątkowym. spróbuj zlokalizować, gdzie jest zamek, uzyskaj całość, na jakie zasoby czekają iw jakim stanie występują warunki wyścigowe.

Zekta Chan
źródło
nie, rejestrowanie jest tutaj twoim wrogiem - kiedy wprowadzasz powolne logowanie, zmieniasz zachowanie programu do punktu, w którym łatwo jest uzyskać program, który działa idealnie z włączonym rejestrowaniem, ale blokuje się, gdy rejestrowanie jest wyłączone. Jest to ten sam problem, jaki miałbyś podczas uruchamiania programu na pojedynczym, a nie na wielordzeniowym procesorze.
gbjbaanb
@ gbjbaanb, myślę, że mówienie, że twój wróg jest zbyt surowe. Być może słuszne byłoby powiedzenie, że to twój najlepszy przyjaciel, który raz na jakiś czas cię zawodzi. Zgadzam się z kilkoma innymi osobami na tej stronie, które twierdzą, że logowanie jest dobrym pierwszym krokiem do podjęcia, po tym, jak badanie kodu nie powiodło się - często (właściwie przez większość czasu, z mojego doświadczenia) zlokalizuje prostą strategię logowania problem łatwo i gotowe. W przeciwnym razie skorzystaj z innych metod, ale nie sądzę, że dobrym pomysłem jest unikanie próbowania tego, co jest najczęściej najlepszym narzędziem do pracy tylko dlatego, że nie zawsze jest pomocne.
Don Hatch
0

To pytanie mnie pociąga;) Po pierwsze, uważaj się za szczęściarza, ponieważ byłeś w stanie konsekwentnie odtwarzać problem za każdym razem. Jeśli za każdym razem otrzymujesz ten sam wyjątek z tym samym śledzeniem stosu, powinno to być dość proste. Jeśli nie, to nie ufaj tak bardzo stosowi śledzenia, zamiast tego po prostu monitoruj dostęp do obiektów globalnych i jego zmiany stanu podczas wykonywania.


źródło
0

Jeśli musisz debugować zakleszczenia, już masz kłopoty. Z reguły używaj zamków przez możliwie najkrótszy czas - lub wcale, jeśli to możliwe. Należy unikać każdej sytuacji, w której bierzesz blokadę, a następnie przechodzisz na nietrywialny kod.

Zależy to oczywiście od środowiska programistycznego, ale powinieneś przyjrzeć się takim rzeczom, jak sekwencyjne kolejki, które mogą umożliwić dostęp do zasobu tylko z jednego wątku.

A potem jest stara, ale niezawodna strategia: przypisuj „poziom” do każdej blokady, zaczynając od poziomu 0. Jeśli przejmiesz blokadę poziomu 0, nie będziesz mieć żadnych innych blokad. Po przyjęciu blokady poziomu 1 możesz wziąć blokadę poziomu 0. Po przyjęciu blokady na poziomie 10 możesz wziąć blokady na poziomie 9 lub niższym itp.

Jeśli nie możesz tego zrobić, musisz naprawić swój kod, ponieważ wpadniesz w impas.

gnasher729
źródło