Czego nauczyłeś się z projektu, który prawie / właściwie zawiódł z powodu złej wielowątkowości? [Zamknięte]

11

Czego nauczyłeś się z projektu, który prawie / właściwie zawiódł z powodu złej wielowątkowości?

Czasami framework narzuca pewien model wątkowania, który sprawia, że ​​trudniej jest uzyskać porządek o rząd wielkości.

Jeśli chodzi o mnie, muszę jeszcze wyjść z ostatniej porażki i uważam, że lepiej dla mnie nie pracować nad niczym, co ma związek z wielowątkowością w tych ramach.

Przekonałem się, że byłem dobry w problemach z wielowątkowością, które mają proste rozwidlenie / łączenie i gdzie dane przemieszczają się tylko w jednym kierunku (podczas gdy sygnały mogą przemieszczać się w kierunku kołowym).

Nie jestem w stanie obsłużyć GUI, w którym część pracy można wykonać tylko na ściśle serializowanym wątku („główny wątek”), a inną pracę można wykonać tylko na dowolnym wątku oprócz głównego wątku („wątki robocze”), oraz gdzie dane i komunikaty muszą przemieszczać się we wszystkich kierunkach między N składowymi (w pełni połączony wykres).

W momencie, gdy zostawiłem ten projekt na inny, wszędzie występowały problemy z impasem. Słyszałem, że 2-3 miesiące później kilku innym programistom udało się naprawić wszystkie problemy z impasem do tego stopnia, że ​​można je wysłać do klientów. Nigdy nie udało mi się znaleźć tej brakującej wiedzy, której mi brakuje.

Coś w projekcie: liczba identyfikatorów wiadomości (wartości całkowite opisujące znaczenie zdarzenia, które można wysłać do kolejki komunikatów innego obiektu, niezależnie od wątków) wynosi kilka tysięcy. Unikalne ciągi (wiadomości użytkownika) również mają około tysiąca.

Dodany

Najlepszą analogią, jaką otrzymałem od innego zespołu (niezwiązaną z moimi wcześniejszymi lub obecnymi projektami), było „umieszczenie danych w bazie danych”. („Baza danych” odnosząca się do centralizacji i aktualizacji atomowych.) W graficznym interfejsie użytkownika podzielonym na wiele widoków, wszystkie działające w tym samym „głównym wątku”, a wszystkie operacje podnoszenia ciężarów innych niż GUI są wykonywane w poszczególnych wątkach roboczych, dane aplikacji powinny być przechowywane w jednym pliku, który działa jak baza danych, i pozwól „bazie danych” obsłużyć wszystkie „aktualizacje atomowe” obejmujące nietrywialne zależności danych. Wszystkie pozostałe części GUI obsługują tylko rysowanie ekranu i nic więcej. Części interfejsu użytkownika mogą buforować pliki, a użytkownik nie zauważy, czy są ułamkowe przez ułamek sekundy, jeśli są odpowiednio zaprojektowane. Ta „baza danych” jest również znana jako „dokument” w architekturze Document-View. Niestety - nie, moja aplikacja faktycznie przechowuje wszystkie dane w widokach. Nie wiem, dlaczego tak było.

Współtwórcy:

(autorzy nie muszą używać prawdziwych / osobistych przykładów. Mile widziane są również wnioski z niepotwierdzonych przykładów, jeśli sam ocenisz je jako wiarygodne).

rwong
źródło
Nie czytam tego, co każdy
deweloper
Myślę, że umiejętność „myślenia w wątkach” jest w pewnym sensie talentem, a mniej czymś, czego można się nauczyć z braku lepszego sformułowania. Znam wielu programistów, którzy pracują z systemami równoległymi od bardzo dawna, ale dusili się, jeśli dane musiały iść w więcej niż jednym kierunku.
dauphic

Odpowiedzi:

13

Moja ulubiona lekcja - bardzo ciężko wygrana! - jest to, że w programie wielowątkowym program planujący jest podstępną świnią, która cię nienawidzi. Jeśli coś pójdzie nie tak, zrobią to, ale w nieoczekiwany sposób. Zrozum cokolwiek złego, a będziesz ścigać dziwne błędy heisen-bugs (ponieważ każda dodana instrumentacja zmieni taktowanie i da ci inny wzorzec przebiegu).

Jedynym rozsądnym sposobem na rozwiązanie tego problemu jest ścisłe przekierowanie obsługi wątków na niewielki fragment kodu, który zapewnia prawidłowe działanie i który jest bardzo konserwatywny pod względem zapewniania, że ​​blokady są odpowiednio trzymane (i przy globalnie stałej kolejności pozyskiwania) . Najłatwiej to zrobić, nie współużytkując pamięci (lub innych zasobów) między wątkami, z wyjątkiem komunikatów, które muszą być asynchroniczne; który pozwala pisać wszystko inne w stylu, który nie uwzględnia wątków. (Bonus: skalowanie do wielu komputerów w klastrze jest znacznie łatwiejsze).

Donal Fellows
źródło
+1 za „nie współużytkowanie pamięci (lub innych zasobów) między wątkami, z wyjątkiem przesyłania komunikatów, które muszą być asynchroniczne;”
Nemanja Trifunovic
1
Jedyny sposób? Co z niezmiennymi typami danych?
Aaronaught
is that in a multithreaded program the scheduler is a sneaky swine that hates you.- nie, nie robi, robi dokładnie to, co
kazałeś
@Aaronaught: Wartości globalne przekazywane przez referencję, nawet jeśli niezmienne, nadal wymagają globalnej GC, a to przywraca całą masę globalnych zasobów. Możliwość zarządzania pamięcią na wątek jest przyjemna, ponieważ pozwala pozbyć się całej gamy globalnych blokad.
Donal Fellows
Nie chodzi o to, że nie można przekazywać wartości typów innych niż podstawowe przez referencję, ale że wymaga to wyższych poziomów blokowania (np. „Właściciel” trzyma referencję, dopóki nie wróci jakaś wiadomość, co łatwo zepsuć w trakcie konserwacji) lub złożony kod w mechanizmie przesyłania wiadomości, aby przenieść własność. Lub organizujesz wszystko i nie rozmawiasz w drugim wątku, co jest znacznie wolniejsze (i tak musisz to zrobić, przechodząc do gromady). Łatwiej jest przejść do sedna i w ogóle nie dzielić pamięci.
Donal Fellows
6

Oto kilka podstawowych lekcji, o których mogę teraz pomyśleć (nie z projektów zakończonych niepowodzeniem, ale z rzeczywistych problemów widocznych w rzeczywistych projektach):

  • Staraj się unikać blokowania połączeń podczas trzymania udostępnionego zasobu. Typowy wzór zakleszczenia polega na tym, że wątek przechwytuje muteks, wykonuje wywołanie zwrotne, blokuje połączenia zwrotne na tym samym muteksie.
  • Chroń dostęp do wszelkich wspólnych struktur danych za pomocą mutex / sekcji krytycznej (lub użyj tych bez blokady - ale nie wymyślaj własnych!)
  • Nie zakładaj atomowości - użyj atomowych interfejsów API (np. InterlockedIncrement).
  • RTFM dotyczący bezpieczeństwa wątków używanych bibliotek, obiektów lub interfejsów API.
  • Skorzystaj z dostępnych prymitywów synchronizacji, np. Zdarzeń, semaforów. (Używając ich, zwróć szczególną uwagę na to, że wiesz, że jesteś w dobrym stanie - widziałem wiele przykładów zdarzeń sygnalizowanych w złym stanie, aby zdarzenia lub dane mogły zostać utracone)
  • Załóżmy, że wątki mogą być wykonywane jednocześnie i / lub w dowolnej kolejności, a kontekst może przełączać się między wątkami w dowolnym momencie (chyba że w systemie operacyjnym, który daje inne gwarancje).
Guy Sirton
źródło
6
  • Cały projekt GUI powinien być wywoływany tylko z głównego wątku . Zasadniczo nie powinieneś umieszczać pojedynczego (.net) „invoke” w GUI. Wielowątkowość powinna utknąć w osobnych projektach, które obsługują wolniejszy dostęp do danych.

Odziedziczyliśmy część, w której projekt GUI wykorzystuje tuzin wątków. Daje tylko problemy. Zakleszczenia, problemy z wyścigami, połączenia GUI między wątkami ...

Carra
źródło
Czy „projekt” oznacza „montaż”? Nie rozumiem, w jaki sposób rozkład klas między zestawami spowodowałby problemy z wątkami.
nikie
W moim projekcie jest to rzeczywiście zgromadzenie. Ale najważniejsze jest to, że cały kod w tych folderach musi być wywoływany z głównego wątku, bez wyjątków.
Carra,
Nie sądzę, aby ta zasada miała ogólne zastosowanie. Tak, nigdy nie należy wywoływać kodu GUI z innego wątku. Ale sposób dystrybucji klas do folderów / projektów / zespołów jest niezależną decyzją.
nikie
1

Java 5 i nowsze wersje mają moduły wykonawcze, które mają ułatwić życie w obsłudze wielowątkowych programów w stylu łączenia widelca.

Używaj ich, to usunie wiele bólu.

(i tak, nauczyłem się z projektu :))


źródło
1
Aby zastosować tę odpowiedź do innych języków - w miarę możliwości korzystaj z wysokiej jakości ram przetwarzania równoległego udostępnianych przez ten język. (Jednak tylko czas pokaże, czy framework jest naprawdę świetny i bardzo użyteczny.)
rwong
1

Mam doświadczenie w twardych systemach osadzonych w czasie rzeczywistym. Nie można testować pod kątem braku problemów spowodowanych wielowątkowością. (Czasami możesz potwierdzić obecność). Kod musi być możliwy do udowodnienia. Tak więc najlepsza praktyka dotycząca dowolnej interakcji wątków.

  • Zasada nr 1: KISS - Jeśli nie potrzebujesz nici, nie obracaj nic. Serializuj jak najwięcej.
  • Reguła # 2: Nie łam # 1.
  • # 3 Jeśli nie możesz udowodnić na podstawie recenzji, że jest poprawna, to nie jest.
mattnz
źródło
+1 dla reguły 1. Pracowałem nad projektem, który początkowo miał być blokowany, aż do ukończenia kolejnego wątku - w zasadzie wywołania metody! Na szczęście postanowiliśmy zrezygnować z tego podejścia.
Michael K
# 3 FTW. Lepiej spędzać godziny walcząc ze schematami blokowania czasu lub czymkolwiek innym, czego używasz, aby udowodnić, że to jest dobre niż miesiące, zastanawiając się, dlaczego czasami się rozpada.
1

Bardzo pomocna była analogia z lekcji wielowątkowości, którą wziąłem w zeszłym roku. Synchronizacja wątków jest jak sygnał drogowy chroniący skrzyżowanie (dane) przed użyciem przez dwa samochody (wątki) jednocześnie. Błędem wielu programistów jest zmienianie świateł na czerwono w większości miast, aby przepuścić jeden samochód, ponieważ uważają, że zbyt trudno lub niebezpiecznie jest ustalić dokładny sygnał, którego potrzebują. Może to działać dobrze, gdy ruch jest mały, ale doprowadzi do blokowania w miarę wzrostu aplikacji.

Jest to coś, co już znałem teoretycznie, ale po tej klasie analogia naprawdę się ze mną utknęła i byłem zdumiony, jak często potem badałem problem z wątkami i znajdowałem jedną wielką kolejkę lub przerywałem wyłączanie wszędzie podczas zapisu do zmiennej tylko dwa używane wątki lub muteksy są trzymane przez długi czas, kiedy można je zrefaktoryzować, aby całkowicie tego uniknąć.

Innymi słowy, niektóre z najgorszych problemów z wątkami są powodowane przez nadmierną liczbę prób uniknięcia problemów z wątkami.

Karl Bielefeldt
źródło
0

Spróbuj zrobić to jeszcze raz.

Przynajmniej dla mnie różnicą była praktyka. Po kilkukrotnym wykonaniu pracy wielowątkowej i rozproszonej po prostu się zorientujesz.

Myślę, że debugowanie jest naprawdę tym, co utrudnia. Mogę debugować wielowątkowy kod za pomocą VS, ale naprawdę mam całkowitą stratę, jeśli muszę użyć gdb. Prawdopodobnie moja wina.

Kolejną rzeczą, o której dowiadujemy się więcej, jest struktura danych bez blokady.

Myślę, że to pytanie można naprawdę poprawić, jeśli określisz ramy. Na przykład pule wątków i procesy działające w tle .NET naprawdę różnią się od QThread. Zawsze jest kilka gotowych specyficznych dla platformy.

Vitor Py
źródło
Interesuje mnie słuchanie historii z dowolnych ram, ponieważ uważam, że z każdego z nich można się nauczyć, zwłaszcza tych, na które nie byłem narażony.
rwong
1
debugery są w dużej mierze bezużyteczne w środowisku wielowątkowym.
Pemdas,
Mam już wielowątkowe znaczniki wykonania, które mówią mi, na czym polega problem, ale nie pomogą mi go rozwiązać. Sedno mojego problemu polega na tym, że „zgodnie z obecnym projektem nie mogę przekazać komunikatu X do obiektu Y w ten sposób (sekwencję); należy go dodać do gigantycznej kolejki i ostatecznie zostanie przetworzony; ale z tego powodu , nie ma sposobu, aby wiadomości pojawiały się użytkownikowi we właściwym czasie - zawsze dzieje się to anachronicznie i powoduje, że użytkownik jest bardzo, bardzo zdezorientowany. Być może trzeba będzie dodać paski postępu, anulować przyciski lub komunikaty o błędach w miejscach, które nie powinny mam te . ”
rwong
0

Nauczyłem się, że wywołania zwrotne z modułów niższego poziomu do modułów wyższego poziomu są wielkim złem, ponieważ powodują nabywanie blokad w odwrotnej kolejności.

Sergej Zagursky
źródło
wywołania zwrotne nie są złe ... fakt, że robią cokolwiek innego niż zrywanie nici, jest prawdopodobnie źródłem zła. Byłbym wysoce podejrzany o każde wywołanie zwrotne, które nie wysłałoby tokena do kolejki wiadomości.
Pemdas,
Rozwiązanie problemu optymalizacji (np. Minimalizacja f (x)) jest często realizowane przez dostarczenie wskaźnika do funkcji f (x) w procedurze optymalizacji, która „odzywa ją”, szukając minimum. Jak zrobiłbyś to bez oddzwaniania?
quant_dev
1
Nie głosuj negatywnie, ale oddzwanianie nie jest złe. Wywoływanie oddzwaniania z blokadą jest złe. Nie dzwoń do niczego wewnątrz zamka, jeśli nie wiesz, czy może się zablokować lub poczekać. Obejmuje to nie tylko wywołania zwrotne, ale także funkcje wirtualne, funkcje API, funkcje w innych modułach („wyższy poziom” lub „niższy poziom”).
nikie
@nikie: Jeśli podczas oddzwaniania należy zablokować blokadę, należy zaprojektować resztę interfejsu API w celu ponownego wysłania (trudne!) lub fakt, że trzymasz blokadę, musi być udokumentowaną częścią interfejsu API ( niefortunne, ale czasami wszystko, co możesz zrobić).
Donal Fellows
@Donal Fellows: Jeśli podczas oddzwaniania trzeba przytrzymać blokadę, powiedziałbym, że masz wadę projektową. Jeśli naprawdę nie ma innego wyjścia, to tak, z całą pewnością udokumentuj to! Tak jak w przypadku dokumentacji, jeśli wywołanie zwrotne zostanie wywołane w wątku w tle. To część interfejsu.
nikie