Wątki i sygnały POSIX

81

Próbowałem zrozumieć zawiłości interakcji wątków POSIX i sygnałów POSIX. W szczególności interesuje mnie:

  • Jaki jest najlepszy sposób kontrolowania, do którego wątku jest dostarczany sygnał (zakładając, że nie jest to śmiertelne)?
  • Jaki jest najlepszy sposób poinformowania innego wątku (który może być faktycznie zajęty), że sygnał dotarł? (Wiem już, że używanie zmiennych warunkowych pthread z modułu obsługi sygnału jest złym pomysłem).
  • Jak mogę bezpiecznie obsługiwać przekazywanie informacji o pojawieniu się sygnału do innych wątków? Czy to musi się zdarzyć w programie obsługi sygnału? (Generalnie nie chcę zabijać innych wątków; potrzebuję znacznie subtelniejszego podejścia.)

Aby dowiedzieć się, dlaczego tego chcę, badam, jak przekonwertować pakiet TclX do obsługi wątków lub podzielić go i przynajmniej utworzyć kilka przydatnych części obsługujących wątki. Sygnały to jedna z tych części, która jest szczególnie interesująca.

Donal Fellows
źródło

Odpowiedzi:

48
  • Jaki jest najlepszy sposób kontrolowania, do którego wątku dostarczany jest sygnał?

Jak wskazał @ zoli2k, jawne wyznaczenie pojedynczego wątku do obsługi wszystkich sygnałów, które chcesz obsłużyć (lub zestawu wątków, z których każdy ma określone obowiązki), jest dobrą techniką.

  • Jaki jest najlepszy sposób, aby poinformować inny wątek (który może być faktycznie zajęty), że sygnał dotarł? [...]
  • Jak mogę bezpiecznie obsługiwać przekazywanie informacji o pojawieniu się sygnału do innych wątków? Czy to musi się zdarzyć w programie obsługi sygnału?

Nie powiem „najlepiej”, ale oto moja rekomendacja:

Zablokuj wszystkie żądane sygnały main, tak aby wszystkie wątki dziedziczyły tę maskę sygnału. Następnie utwórz specjalny wątek odbierający sygnał jako pętlę zdarzeń sterowaną sygnałem, wysyłając nowo przybyłe sygnały jako inną komunikację wewnątrz wątku .

Najłatwiej to zrobić, aby wątek akceptował sygnały w pętli za pomocą sigwaitinfolubsigtimedwait . Wątek następnie w jakiś sposób konwertuje sygnały, być może rozgłasza pthread_cond_t, budzi inne wątki z większą liczbą operacji we / wy, kolejkuje polecenie w kolejce bezpiecznej dla wątków specyficznej dla aplikacji, cokolwiek.

Alternatywnie, specjalny wątek mógłby pozwolić na dostarczanie sygnałów do obsługi sygnału, zdejmując maskę przed dostarczeniem tylko wtedy, gdy jest gotowy do obsługi sygnałów. (Dostawa sygnału przez teleskopowe są bardziej podatne na błędy niż akceptacji sygnału poprzez sigwaitrodzinę, jednak). W tym przypadku obsługi sygnału wykonuje odbiornika niektóre proste i działanie sygnału bezpieczny asynchroniczny: ustawienie sig_atomic_tflagi, wzywając sigaddset(&signals_i_have_seen_recently, latest_sig), write() bajt do nieblokującego potoku własnego itp. Następnie, z powrotem w swojej zamaskowanej pętli głównej, wątek przekazuje odbiór sygnału do innych wątków, jak powyżej.

( AKTUALIZACJA @caf słusznie wskazuje, że sigwaitpodejścia są lepsze).

pilcrow
źródło
1
To o wiele bardziej przydatna odpowiedź, zwłaszcza, że ​​można jej używać również do obsługi sygnałów, które nie są śmiertelne. Dzięki!
Donal Fellows
1
Najłatwiej jest, jeśli wątek obsługi sygnału w ogóle nie instaluje programów obsługi sygnału - zamiast tego wykonuje pętlę wokół sigwaitinfo()(lub sigtimedwait()), a następnie wysyła je do reszty aplikacji, jak opisano w ostatnim akapicie.
kawiarnia
@caf, rzeczywiście. Zaktualizowano
pilcrow
14

Zgodnie ze standardem POSIX wszystkie wątki powinny mieć ten sam PID w systemie i za pomocą pthread_sigmask()można zdefiniować maskę blokowania sygnału dla każdego wątku.

Ponieważ dozwolone jest zdefiniowanie tylko jednego programu obsługi sygnału na PID, wolę obsługiwać wszystkie sygnały w jednym wątku i wysyłać, pthread_cancel()jeśli działający wątek wymaga anulowania. Jest to preferowana metoda, pthread_kill()ponieważ umożliwia definiowanie funkcji czyszczenia dla wątków.

W niektórych starszych systemach, z powodu braku odpowiedniej obsługi jądra, działające wątki mogą mieć inny PID niż PID wątku macierzystego. Zobacz często zadawane pytania dotyczące obsługi sygnałów w linuxThreads w systemie Linux 2.4 .

zoli2k
źródło
W tym, co mówisz, „wdrożone” oznacza co? Ponadto nie jest poprawne nukeowanie innych wątków w odpowiedzi na sygnał (SIGHUP i SIGWINCH wymagają większej subtelności), a jednak używanie zmiennych warunkowych do powiadamiania innych wątków jest niebezpieczne. Słaba odpowiedź.
Donal Fellows
1
Usunąłem mój głos negatywny, ale nadal nie jest to wystarczająca odpowiedź, ponieważ nie mogę po prostu zabijać wątków w odpowiedzi na sygnał. W niektórych przypadkach zamierzam kolejkować zdarzenia lokalnie w odpowiedzi, w innych muszę bardzo ostrożnie zerwać wątki (BTW, mam już większość maszyn do wykonywania tych części; to połączenia z systemem operacyjnym sygnały, których brakuje).
Donal Fellows
1
@ zoli2k: Niedawno próbowałem uruchomić make menuconfigze świeżo sklonowaną gałęzią git master uClibc. Nie jest to wybór pomiędzy starymi LinuxThreads i nowszej NPTL jako implementacji wątku POSIX, ale pomoc od roku 2012 nadal odradza wyborze NPTL. Dlatego we współczesnych wbudowanych systemach Linux nadal często można zobaczyć przestarzałą implementację LinuxThreads, nawet jeśli system ma wystarczająco nowsze jądro Linuksa.
FooF
3

Gdzie jestem do tej pory:

  • Sygnały występują w różnych głównych klasach, z których niektóre i tak powinny zazwyczaj po prostu zabić proces (SIGILL), a niektóre nigdy nie wymagają żadnej czynności (SIGIO; i tak łatwiej jest po prostu wykonać asynchroniczne IO). Te dwie klasy nie wymagają żadnego działania.
  • Niektóre sygnały nie wymagają natychmiastowej obsługi; takie jak SIGWINCH można ustawić w kolejce, aż będzie to wygodne (tak jak zdarzenie z X11).
  • Te trudne to te, w których chcesz im odpowiedzieć, przerywając to, co robisz, ale bez usuwania nawet wątku. W szczególności SIGINT w trybie interaktywnym powinien pozostawić responsywność.

Mam jeszcze do sortowania signalvs sigaction, pselect, sigwait, sigaltstack, i cała masa innych bitów i kawałków POSIX (i nie-POSIX) API.

Donal Fellows
źródło
3

Sygnały IMHO, Unix V i wątki posix nie mieszają się dobrze. Unix V to 1970. POSIX to 1980;)

Istnieją punkty anulowania i jeśli zezwolisz na sygnały i pthreads w jednej aplikacji, w końcu skończysz pisanie pętli wokół każdego połączenia, które może zaskakująco zwrócić EINTR.

Więc to, co zrobiłem w (nielicznych) przypadkach, w których musiałem programować wielowątkowość w systemie Linux lub QNX, polegało na zamaskowaniu wszystkich sygnałów dla wszystkich (z wyjątkiem jednego) wątków.

Kiedy nadejdzie sygnał Unix V, proces przełącza stos (to było tyle współbieżności w Unix V, ile można było uzyskać w procesie).

Jak wskazują inne posty, teraz może być możliwe poinformowanie Systemu, który wątek posix będzie ofiarą tego przełączania stosu.

Kiedyś udało ci się uruchomić wątek obsługujący sygnał, pozostaje pytanie, jak przekształcić informacje o sygnale w coś cywilizowanego, którego mogą użyć inne wątki. Wymagana jest infrastruktura do komunikacji między wątkami. Jednym z użytecznych wzorców jest wzorzec aktora, w którym każdy z twoich wątków jest celem dla jakiegoś mechanizmu przesyłania wiadomości w procesie.

Tak więc, zamiast anulować inne wątki lub zabijać je (lub inne dziwne rzeczy), powinieneś spróbować skierować Signal z kontekstu Signal do wątku obsługującego Signal, a następnie użyć mechanizmów komunikacji wzorców aktorów, aby wysłać semantycznie użyteczne wiadomości do tych aktorów, którzy potrzebują informacji związanych z sygnałem.

user2173833
źródło