2010-05-14 11 views
9

Część aplikacji, nad którą pracuję, to prosty serwer oparty na pthread, który komunikuje się przez gniazdo TCP/IP. Piszę to w C, ponieważ będzie działał w środowisku z ograniczoną pamięcią. Moje pytanie brzmi: co powinien zrobić program, jeśli jeden z wątków napotka obiekt malloc(), który zwraca NULL? Możliwości, które do tej pory wymyśliłem:W jaki sposób wielowątkowa aplikacja C radzi sobie z nieudanym malloc()?

  1. Bez specjalnej obsługi. Niech funkcja malloc() zwróci wartość NULL i pozwoli na jej dereferencję, tak aby cała sprawa uległa uszkodzeniu.
  2. Zamknij natychmiast niepowodzenie malloc(), wywołując abort() lub exit (-1). Załóżmy, że środowisko wszystko oczyści.
  3. Wyskocz z głównej pętli zdarzeń i spróbuj pthread_join() wszystkich wątków, a następnie zamknij system.

Pierwsza opcja jest oczywiście najłatwiejsza, ale wydaje się bardzo błędna. Drugi również wydaje się błędny, ponieważ nie wiem dokładnie, co się stanie. Trzecia opcja wydaje się kusząca, z wyjątkiem dwóch problemów: po pierwsze, wszystkie wątki nie muszą być łączone z powrotem do głównego wątku w normalnych okolicznościach, a po drugie, aby zakończyć wykonywanie wątku, większość pozostałych wątków będzie musiała wywoływać malloc () jeszcze raz.

Co mam zrobić?

+4

Po pierwszej lekturze, myślałem 2) powiedział: "Załóżmy, że rząd wszystko posprząta". :) – Rob

+1

@Rob czytanie zbyt wiele wiadomości o wycieku ropy, co? –

+0

@Rob - tak, musimy tylko deregulować 'malloc()', aby zachęcić większą konkurencję między procesami .. co może pójść nie tak? –

Odpowiedz

4

Jest to jeden z powodów, dla których space/rad hard systems generalnie zabrania dynamicznego przydzielania pamięci. Gdy ulegnie awarii, bardzo trudno jest "wyleczyć" awarię. Masz kilka opcji:

  • Nie musisz używać wbudowanej biblioteki libc malloc() (w ogóle lub jak zwykle). Możesz wykonać wrap malloc(), aby wykonać dodatkową pracę przy niepowodzeniach, takich jak powiadamianie o czymś innym. Jest to pomocne przy używaniu czegoś takiego jak watchdog. Możesz także użyć full blown garbage collector, ale nie polecam go. Lepiej zidentyfikować i naprawić wycieki.
  • W zależności od przestrzeni dyskowej i złożoności rzadko przydzielane bloki alokowane na dysku mogą być mapowane na dysk. Ale tutaj, typowo, patrzysz tylko na kilka KB oszczędności w pamięci fizycznej.
  • Możesz użyć statycznej puli pamięci i własnego malloc(), które nie będą go przesadzać. Jeśli profilowałeś swoje użycie sterty w znacznym stopniu (przy użyciu narzędzia takiego jak Valgrind o masie lub podobnej), możesz rozsądnie powiększyć wielkość puli.

Jednak to, co większość z tych sugestii sprowadzają się nie ufa/korzystania z systemu malloc() jeśli awaria nie jest rozwiązaniem.

W twoim przypadku, myślę, że najlepszą rzeczą jaką możesz zrobić, to pewny watchdog notyfikację w przypadku malloc() nie powiedzie się, więc, że proces (lub całego systemu) może być ponownie uruchomiony. Nie chcesz, aby wyglądał "żywy i działający", gdy znajdujesz się w martwym punkcie. Może to być tak proste, jak po prostu odłączenie pliku.

Napisz bardzo szczegółowe logi. Jaki plik/linia/funkcja spowodowała awarię?

Jeśli próba uzyskania zaledwie kilku KB kończy się niepowodzeniem, to jest to dobry znak, że proces nie może być kontynuowany w niezawodny sposób. Jeśli nie uda się zdobyć kilkuset MB, możesz odzyskać i kontynuować. Na podstawie tego żetonu każde działanie, jakie podejmiesz, powinno opierać się na tym, ile pamięci próbujesz uzyskać, i czy połączenia w celu przydzielenia o wiele mniejszego rozmiaru nadal się powiodą.

Jedną z rzeczy, których nigdy nie chcesz robić, jest operowanie wskaźnikami NULL i awarie. Jest po prostu niechlujny, nie dostarcza użytecznych informacji o tym, gdzie coś poszło nie tak i daje wrażenie, że twoje oprogramowanie ma niską/niestabilną jakość.

+1

Wow, dzięki za szczegółową odpowiedź. Myślę, że twoja sugestia powiadamiania strażnika dobrze by działała w moim przypadku. W tym programie żądam tylko małych bitów pamięci (prawie zawsze poniżej 1KB i najczęściej poniżej 100 bajtów), ale nie wiadomo z góry, ile z nich będę potrzebował, więc nic nie mogę zrobić, aby zwolnić pamięć w przypadku, gdy malloc zwróci NULL. – ipartola

+0

@ipartola - tak, jeśli nie możesz przydzielić 1k, coś jest nie tak. Tak czy inaczej, rozsądnie inteligentny organ nadzorujący powinien wykonać to zadanie, dopóki nie naprawisz każdego wycieku/etc i naprawdę dostosujesz wszystko, co działa, by współpracować na małej przestrzeni. –

4

Nie ma nic złego w opcji 2. Nie musisz zakładać - exit() wychodzi z procesu, co oznacza, że ​​wszystkie wątki są zrywane i wszystko jest czyszczone.

Nie zapomnij spróbuj zarejestrować miejsca, w których nastąpiło niepowodzenie alokacji.

+0

"wszystko" jest nieco optymistyczne. Na przykład musisz upewnić się, że pliki na dysku są prawidłowo czyszczone. – MSalters

+0

"wszystko jest czyszczone" w tym sensie, że zasoby są zwracane do systemu operacyjnego; ale lepiej mieć nadzieję, że nie jest to * twój * system podtrzymywania życia! Konsekwencje niedoterminacji systemu mogą nie być tak proste i zależą od aplikacji. – Clifford

+3

Jeśli mój system podtrzymywania życia jest serwerem akceptującym połączenia TCP/IP, muszę już być zmęczony życiem! – caf

0

W zależności od Twojej architektury myślę.

Czy błąd w oznaczeniu malloc() oznacza, że ​​tylko ten wątek nie może być kontynuowany lub czy cały proces został przerwany w takiej sytuacji?

Zasadniczo, gdy pamięć jest naprawdę ciasna (tj. Środowiska mikroprocesorowe), dobrym pomysłem jest uniknięcie CAŁKOWITEJ alokacji pamięci dynamicznej, aby uniknąć takich problemów.

2

Istnieje czwarta opcja: zwolnij trochę pamięci (pamięci podręczne zawsze są dobrymi kandydatami) i spróbuj ponownie.

Jeśli nie możesz sobie na to pozwolić, wybrałbym opcję 2 (rejestrowanie lub drukowanie jakiegoś komunikatu o błędzie, oczywiście) ... Jedynym problemem związanym z czyszczeniem byłaby zamykanie otwartych połączeń sieciowych w uporządkowany sposób, aby klienci wiedzą, że aplikacja po drugiej stronie jest zamykana, zamiast znaleźć nieoczekiwany problem z łącznością.

+0

Tak, myślałem/czytałem o tej opcji. W tym przypadku nie ma takiej opcji, ponieważ nie ma żadnych pamięci podręcznych. Czy gniazda/deskryptory plików nie są automatycznie niszczone po wyjściu z programu? – ipartola

+1

Powinny być, ponieważ są obsługiwane przez jądro. Zostaną one zamknięte automatycznie po zakończeniu procesu. – Wyzard

+0

Tak, są zamknięte, ale być może twoja aplikacja ma jakiś specyficzny protokół do rozłączenia, a nie tylko wyłączenie strumienia. – fortran

0

Z własnego doświadczenia mogę stwierdzić, że częstotliwość błędów malloc jest często zawyżona. Na przykład w Linuksie zwykłe "rozwiązanie" to wariant 2, a nie otrzymujesz błędu malloc. Proces nagle umiera. W większych systemach aplikacja ma tendencję do śmierci, ponieważ użytkownik lub watchdog ją zabija, gdy tylko zamiana sprawi, że przestanie reagować.

To sprawia, że ​​oczyszczanie jest nieco trudniejsze, a także sprawia, że ​​trudno jest znaleźć ogólne rozwiązanie.

+0

Myślę, że to nie będzie powszechne. W przypadkach, w których musiałbym przydzielać megabajty na raz, prawdopodobnie miałbym więcej swobody, aby zwolnić pamięć podręczną, itp. W tym przypadku przydzielam bardzo mało pamięci, więc jedynym sposobem, w jaki może się to naprawdę stać, jest proces w systemie szaleje. Po prostu robię to, aby zrobić to pół-poprawnie bez zaśmiecania kodu źródłowego. – ipartola

0

Czy to działa w systemie operacyjnym? Sugeruje to użycie pthreadów. Czy wiesz, że nawet malloc() kiedykolwiek zwróci NULL? W niektórych systemach (na przykład Linux) usterka wystąpi w funkcji malloc() i będzie obsługiwana przez system operacyjny (przez zabicie procesu) bez zwracania funkcji malloc().

Proponuję przydzielić pulę pamięci podczas inicjowania aplikacji i przydzielić ją zamiast używać malloc() po inicjalizacji. Zapewni to kontrolę nad algorytmem alokacji pamięci i zachowanie po wyczerpaniu pamięci. Jeśli nie ma wystarczającej ilości pamięci dla puli, wystąpi pojedynczy punkt awarii podczas inicjalizacji, zanim aplikacja będzie miała szansę na rozpoczęcie wszystkiego, czego nie może zakończyć.

W systemach czasu rzeczywistego i systemach wbudowanych często stosuje się 'fixed-block memory allocator'. Jeśli twój system operacyjny nie zapewnia usług, można go zaimplementować poprzez wstępne przydzielanie bloków pamięci i umieszczanie ich wskaźników w kolejce. Aby przydzielić blok, bierzesz wskaźnik z kolejki, a aby go zwolnić, umieść go z powrotem w kolejce. Kiedy kolejka jest pusta, pamięć jest wyczerpana i możesz albo zniszczyć i poradzić sobie z błędem, albo zablokować i poczekać, aż inny wątek zwróci trochę pamięci. Możesz utworzyć wiele pul z blokami o różnych rozmiarach, a nawet utworzyć pulę do określonego celu, blokując dokładny rozmiar potrzebny do tego celu.

+0

Ten program będzie działał na systemie o smaku * nix, ale prawdopodobnie z bardzo małą ilością pamięci RAM i teraz jest wymienny. Zastanawiam się, jak często jest wśród programów UNIX, aby przydzielić wszystkie swoje pule pamięci z wyprzedzeniem. Wygląda na to, że może to być przesada w mojej sytuacji, ale na pewno będę o tym pamiętać. – ipartola

Powiązane problemy