Article View: pl.comp.lang.c
Article #267801Re: Ciemna strona C++
From: Marcin ‘Qrczak’ Kowalczyk
Date: Fri, 23 Nov 2007 22:50
Date: Fri, 23 Nov 2007 22:50
166 lines
10897 bytes
10897 bytes
Dnia 23-11-2007, Pt o godzinie 14:13 +0000, Sektor van Skijlen pisze:
> Ciekaw jestem swoją drogą, czy nie kończy się to czasem tym, że programiści
> wkurzeni koniecznością definiowania propagacji po prostu każą się wątkowi
> ukryć "pod osią ciężarówki RuntimeException".
Od Javy 1.4 klasa Throwable ma atrybut cause, typu również Throwable.
A więc każdy wyjątek ma miejsce na opcjonalny inny wyjątek, który był
jego przyczyną. Istnieje kanoniczny sposób na owinięcie dowolnego
wyjątku w RuntimeException (a także w dowolny inny wyjątek, który
umiemy skonstruować):
new RuntimeException(exn)
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Throwable.html
Nie używam Javy, ale mogę się domyślać, że owijanie i odwijanie wyjątków
pochodzących z różnych warstw abstrakcji (niższych w wyższe) jest typowe.
> Zgadza się, ale w stan sleep można wprowadzić wątek nie tylko wywołując sleep
> (mówię w sensie systemowo-ogólnym), ale również w stanie oczekiwania na I/O.
> Tymczasem w Javie InterruptedException jest rzucane tylko przez Thread.sleep
> i Object.wait. InputStream.read przykładowo rzuca tylko IOException!
Zauważyłem. Za to od Javy 1.4 jest interfejs InterruptibleChannel.
Operacje wejścia-wyjścia na takim strumieniu reagują na Thread.interrupt
zamknięciem strumienia i rzuceniem wyjątku ClosedByInterruptException
(a nie InterruptedException). Chyba wszystkie klasy strumieni z NIO
implementują ten interfejs. Można uznać, że "po nowemu" operacje na
strumieniach są już przerywalne.
Nie wykluczam, że osobna klasa wyjątku jest spowodowana tym, że różne
interfejsy wejścia-wyjścia uwzględniały już IOException, ale nie
InterruptedException, więc można było dodać ClosedByInterruptException
pośrednio dziedziczące po IOException bez zmiany deklaracyj wyjątków.
Ale tylko tak zgaduję.
> Super - tylko skoro wątek może być przerwany tylko przez InterruptedException,
> to w jaki sposób następuje "killowanie" wątku javowego? Jeśli nikt nie używa
> żadnego sleep() w środku, to wątku nie można przerwać w ogóle? A jeśli można,
> to jak się to odbywa?
Ktoś z zewnątrz może zrobić temu wątkowi Thread.interrupt. Wtedy
ten wątek zostanie przerwany przy najbliższej okazji, kiedy wykona
przerywalną operację (wait, join, sleep, coś z InterruptibleChannel,
coś z Selector). Przy czym przerwanie Selectora nie powoduje wyjątku.
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.html#interrupt()
Nie wiem, czy w Javie są inne źródła takich przerwań niż Thread.interrupt.
W Kogucie najbliższy odpowiednik takich przerwań (nazwany przeze mnie
sygnałami) nie skutkuje wyjątkiem, tylko wykonaniem funkcji obsługi
sygnału (nie mylić z funkcjami obsługi sygnału w C, które określa się
całkiem inaczej), która może rzucić wyjątkiem, ale może też zrobić coś
innego i potem kontynuować działanie przerwanego kodu. Do tego dochodzi
mechanizm tymczasowego blokowania sygnałów, zintegrowany z różnymi
konstrukcjami języka. Sygnał z kolei może być skutkiem:
- jawnego przerwania wątku przez inny wątek
- sygnału Uniksa skierowanego do procesu (dostaje go wątek przeznaczony
do odbioru sygnałów systemowych, domyślnie jest nim wątek główny)
- kończącej się pamięci (dostaje go wątek odbierający sygnały systemowe)
- zbyt dużego zużycia stosu (dostaje go winowajca)
- zakończenia programu, któremu zostały inne wątki
- forka procesu (za pomocą sygnałów znajdowany jest odpowiedni dla
innych wątków moment wykonania forka)
- porzucenia zablokowanego wątku przez program (zanim wątek zostanie
odśmiecony, jest kopnięty przez sygnał i ma szansę się ładniej zamknąć)
- wzajemnego zakleszczenia się wszystkich wątków
Moje założenie dla Koguta jest takie, żeby typowy kod można było
bezpiecznie przerwać bez poświęcania istotnej części projektu danego
programu na obsługę przerwań. Konstrukcje językowe, które ułatwiają
pisanie kodu bezpiecznego względem synchronicznych wyjątków, miewają
proste uogólnienia, które ułatwiają osiągnięcie bezpieczeństwa również
względem asynchronicznych sygnałów. Chodzi głównie o stosowanie operacji
działających na zakresach kodu zamiast polegania na zmianach stanu
jako efektach ubocznych instrukcji (odpowiednik RAII) i o automatyczne
blokowanie sygnałów przez pewne operacje (kanoniczny przykład to zajęcie
muteksa).
> > Gdyby mógł wystąpić w zupełnie dowolnym momencie, to nie dałoby się
> > pisać poprawnego kodu, bo np. nie da się zagwarantować, że z pewnych
> > dwóch instrukcji wykonają się obie albo żadna.
>
> Oj, nie rozumiem, niby dlaczego. Jeśli wykonuję a + b + c, a drugi plus rzuci
> wyjątkiem (bo akurat ktoś stwierdził, że sprzeda procesowi killa), to jaki
> problem tu może wystąpić?
Jak napisałem, problemem jest brak możliwości zagwarantowania,
że z dwóch sąsiednich instrukcji wykonają się obie albo żadna.
Na przykład niech jedna z tych instrukcji dodaje powiązanie we
współdzielonym słowniku, a druga uaktualnia rozmiar słownika.
Twórcy Javy nie od razu to zauważyli:
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.html#stop()
Sposób, w jaki zostało to poprawione (tzn. Thread.interrupt przerywające
wybrane operacje), nie jest idealny, bo z jednej strony nie umożliwia
wykonania tych operacji bez możliwości przerwania, a z drugiej strony
nie pozwala przerwać czystych obliczeń, które tego nie przewidziały.
Ale przynajmniej pozwala pisać kod, który bezpiecznie jest przerwać
i który po tym przerwaniu program nie nadaje się wyłącznie do awaryjnego
zakończenia.
> > Należy umożliwić wątkowi blokowanie przerywania
>
> Nie. Należy umożliwić wątkowi sprzątanie istniejących zasobów w razie
> przerwania, natomiast na pewno nie odkładanie reakcji na przerwanie ad
> calendas graecas.
Jak widzę, popełniłbyś ten sam błąd co twórcy Javy :-)
Upieram się, że należy umożliwić wątkowi blokowanie przerywania.
To jest naturalne rozwiązanie dla typowego modelu współbieżności:
dla wątków ze współdzieloną pamięcią zawierającą zmieniające swój
stan obiekty.
Uniksowa funkcja sigprocmask (tudzież pthread_sigmask dla programów
wielowątkowych) istnieje nie bez powodu.
Haskell ma trochę inny paradygmat i trochę inny model przerywania wątków.
W dużej mierze podobny, ale jego zachowanie przerwania obliczenia leniwej
zmiennej jest lepsze niż moje. A to dzięki temu, że Haskell rozróżnia
czysto funkcyjny kod od kodu imperatywnego. Dzięki temu wie, które
obliczenia bezpiecznie jest wykonać jeszcze raz, a które nie. Ja tego
nie wiem, więc musiałem przyjąć domyślne blokowanie przerywania przy
leniwych zmiennych. Haskell mógł wykorzystać to, że czysto funkcyjny kod
bezpiecznie jest przerwać w każdym momencie. Za to moja semantyka jest
przydatniejsza w innych sytuacjach. Na przykład Concurrent Haskell nie
rozróżnia muteksów od semaforów, a ja rozróżniam, przez co mnie jest
łatwiej powiązać przerywalność wątku z faktem zajęcia muteksa.
> > Przy czym w C++ nie ma na to szans, bo jest zdecydowanie zbyt wiele
> > miejsc, których nie można przerwać, ale nie ma jak ich automatycznie
> > rozpoznać. Na przykład region między new a przypisaniem gołego wskaźnika
> > do sprytnego wskaźnika nie może być przerywalny (przynajmniej przy
> > standardowym alokatorze).
>
> Moment - o ile ja znam C++, to przerwanie przez wyjątek może nastąpić tylko w
> dwóch miejscach: w operatorze new (wtedy pamięć jest automatycznie usuwana)
> lub po przypisaniu (zakładamy, że w trakcie przypisywania nie wystąpi).
Cały czas mówię o asynchronicznych wyjątkach, mających swoje źródło poza
danym wątkiem. One mogą przyjść w dowolnym momencie, również kiedy wątek
jest "między" alokacją pamięci a przypisaniem wskaźnika. Nie da się tego
załatać przez zadeklarowanie, że "między" nie istnieje, bo w bardziej
rozbudowanym przypadku tam się znajdą jakieś wywołania funkcji itp.,
które musiałbyś zakwalifikować jako reagujące na asynchroniczne wyjątki
bądź nie. Jeśli za dużo operacji będzie przerywalnych, to trudno będzie
pisać bezpieczny kod, bo zdarzą się sytuacje, w których skądinąd
przerywalna operacja akurat w danym kontekście nie może być przerwana.
Przy przesadzie w drugą stronę wątek może wykonywać dużo obliczeń bez
szans na przerwanie. To nie wystarczy.
Najlepszą radą na to, jaką znam, jest związanie z każdym wątkiem
bieżącego stanu zablokowania asynchronicznych sygnałów. Przy czym
okazuje się, że ten stan powinien być trochę bardziej rozbudowany
niż jedna boolowska flaga (u mnie to jest licznik + dodatkowa flaga).
Przedsmakiem tego rozbudowania jest funkcja pthread_setcanceltype
istniejąca obok pthread_setcancelstate.
--
__("< Marcin Kowalczyk
\__/ qrczak@knm.org.pl
^^ http://qrnik.knm.org.pl/~qrczak/
Technical Details
Message-ID:
<1195858230.20523.107.camel@qrnik>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!newsfeed.gazeta.pl!newsfeed.tpinternet.pl!atlantis.news.tpi.pl!news.tpi.pl!not-for-mailReferences:
<fghgeo$spf$1@nemesis.news.tpi.pl> <fghsb4$kpq$1@news.dialog.net.pl> <fgo4c7$mtu$1@news.vectranet.pl> <20071106073039.433f4934@Vorago> <fgqn9m$74j$2@news.vectranet.pl> <618565031.20071107113041@pkierski.net> <fgt6sh$jov$1@news.vectranet.pl> <fgtb6j$nur$1@news.vectranet.pl> <fgv6ba$k14$1@bozon2.softax.pl> <fgvkne$u3b$1@news.vectranet.pl> <fh1tjd$dtt$1@bozon2.softax.pl> <fh2lj6$qkh$1@news.vectranet.pl> <fha3as$pap$1@bozon2.softax.pl> <fhaj8u$e02$1@news.vectranet.pl> <fhf09d$ohq$1@bozon2.softax.pl> <fhfi9l$640$1@news.vectranet.pl> <fhurmu$5d9$1@bozon2.softax.pl> <fhv020$8k1$1@news.vectranet.pl> <fi1bs4$nn3$1@bozon2.softax.pl> <fi27ds$gt1$1@kujawiak.man.lodz.pl> <1195688300.8426.30.camel@qrnik> <fi3ems$7ig$1@kujawiak.man.lodz.pl> <1195734091.8426.77.camel@qrnik> <fi6n5f$fpr$1@kujawiak.man.lodz.pl>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!newsfeed.gazeta.pl!newsfeed.tpinternet.pl!atlantis.news.tpi.pl!news.tpi.pl!not-for-mail From: Marcin ‘Qrczak’ Kowalczyk <qrczak@knm.org.pl> Newsgroups: pl.comp.lang.c Subject: Re: Ciemna strona C++ Date: Fri, 23 Nov 2007 23:50:30 +0100 Organization: tp.internet - http://www.tpi.pl/ Lines: 227 Message-ID: <1195858230.20523.107.camel@qrnik> References: <fghgeo$spf$1@nemesis.news.tpi.pl> <fghsb4$kpq$1@news.dialog.net.pl> <fgo4c7$mtu$1@news.vectranet.pl> <20071106073039.433f4934@Vorago> <fgqn9m$74j$2@news.vectranet.pl> <618565031.20071107113041@pkierski.net> <fgt6sh$jov$1@news.vectranet.pl> <fgtb6j$nur$1@news.vectranet.pl> <fgv6ba$k14$1@bozon2.softax.pl> <fgvkne$u3b$1@news.vectranet.pl> <fh1tjd$dtt$1@bozon2.softax.pl> <fh2lj6$qkh$1@news.vectranet.pl> <fha3as$pap$1@bozon2.softax.pl> <fhaj8u$e02$1@news.vectranet.pl> <fhf09d$ohq$1@bozon2.softax.pl> <fhfi9l$640$1@news.vectranet.pl> <fhurmu$5d9$1@bozon2.softax.pl> <fhv020$8k1$1@news.vectranet.pl> <fi1bs4$nn3$1@bozon2.softax.pl> <fi27ds$gt1$1@kujawiak.man.lodz.pl> <1195688300.8426.30.camel@qrnik> <fi3ems$7ig$1@kujawiak.man.lodz.pl> <1195734091.8426.77.camel@qrnik> <fi6n5f$fpr$1@kujawiak.man.lodz.pl> NNTP-Posting-Host: hjd150.internetdsl.tpnet.pl Mime-Version: 1.0 X-Trace: nemesis.news.tpi.pl 1195858575 15642 79.187.237.150 (23 Nov 2007 22:56:15 GMT) X-Complaints-To: usenet@tpi.pl NNTP-Posting-Date: Fri, 23 Nov 2007 22:56:15 +0000 (UTC) In-Reply-To: <fi6n5f$fpr$1@kujawiak.man.lodz.pl> X-Mailer: Evolution 2.12.1 PLD Linux Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit