Mamy funkcję, do której wywołuje pojedynczy wątek (nazywamy ją głównym wątkiem). W treści funkcji tworzymy wiele wątków roboczych w celu wykonania intensywnej pracy procesora, czekamy na zakończenie wszystkich wątków, a następnie zwracamy wynik w wątku głównym.
W rezultacie wywołujący może korzystać z funkcji naiwnie, a wewnętrznie będzie korzystać z wielu rdzeni.
Jak dotąd wszystko dobrze ..
Problem, jaki mamy, dotyczy wyjątków. Nie chcemy, aby wyjątki w wątkach roboczych powodowały awarię aplikacji. Chcemy, aby obiekt wywołujący funkcję był w stanie przechwycić je w głównym wątku. Musimy przechwytywać wyjątki w wątkach roboczych i przenosić je do głównego wątku, aby mogły się dalej rozwijać.
Jak możemy to zrobić?
Najlepsze, co przychodzi mi do głowy, to:
- Wyłap całą różnorodność wyjątków w naszych wątkach roboczych (std :: wyjątek i kilka naszych własnych).
- Zapisz typ i treść wyjątku.
- Miej odpowiednią instrukcję switch w głównym wątku, która ponownie zgłasza wyjątki dowolnego typu, które zostały zarejestrowane w wątku roboczym.
Ma to oczywistą wadę, ponieważ obsługuje tylko ograniczony zestaw typów wyjątków i wymagałoby modyfikacji po dodaniu nowych typów wyjątków.
źródło
Obecnie jedynym przenośnym sposobem jest zapisanie klauzul catch dla wszystkich typów wyjątków, które możesz chcieć przenieść między wątkami, przechowywanie informacji gdzieś z tej klauzuli catch, a następnie użycie ich później do ponownego wyrzucenia wyjątku. Takie jest podejście przyjęte przez Boost.Exception .
W C ++ 0x będziesz mógł złapać wyjątek za pomocą,
catch(...)
a następnie zapisać go w instancjistd::exception_ptr
usingstd::current_exception()
. Następnie możesz ponownie wrzucić go później z tego samego lub innego wątku za pomocąstd::rethrow_exception()
.Jeśli używasz programu Microsoft Visual Studio 2005 lub nowszego, obsługuje bibliotekę wątków just :: thread C ++ 0x
std::exception_ptr
. (Zastrzeżenie: to jest mój produkt).źródło
Jeśli używasz C ++ 11,
std::future
możesz zrobić dokładnie to, czego szukasz: może automagicznie przechwytywać wyjątki, które trafiają na początek wątku roboczego i przekazywać je do wątku nadrzędnego w punkcie, którystd::future::get
jest nazywa. (Za kulisami dzieje się to dokładnie tak, jak w odpowiedzi @AnthonyWilliams; zostało już zaimplementowane.)Wadą jest to, że nie ma standardowego sposobu, aby „przestać przejmować się” a
std::future
; nawet jego destruktor będzie po prostu blokował, dopóki zadanie nie zostanie wykonane. [EDYCJA, 2017: Zachowanie blokująco-niszczycielskie jest nieprawidłowością tylko w zwróconych pseudo-futures, zstd::async
których i tak nigdy nie powinieneś używać. Normalne futures nie blokują się w ich destruktorze. Ale nadal nie możesz „anulować” zadań, jeśli używaszstd::future
: zadania spełniające obietnice będą nadal działały za kulisami, nawet jeśli nikt już nie słucha odpowiedzi.] Oto przykład zabawki, który może wyjaśnić, co ja oznaczać:Właśnie próbowałem napisać podobny do pracy przykład przy użyciu
std::thread
istd::exception_ptr
, ale coś jest nie tak zstd::exception_ptr
(używając libc ++), więc nie udało mi się jeszcze tego zrobić. :([EDYCJA, 2017:
Nie mam pojęcia, co robiłem źle w 2013 roku, ale jestem pewien, że to moja wina.]
źródło
f
a potememplace_back
to? Nie mógłbyś po prostu zrobić,waitables.push_back(std::async(…));
czy coś przeoczę (kompiluje się, pytanie brzmi, czy może wyciekać, ale nie wiem jak)?wait
zerwania? Coś w rodzaju „gdy tylko jedno z zadań zawodzi, inne nie mają już znaczenia”.async
zwraca się raczej przyszłość niż coś innego). W odniesieniu do „Również, czy tam jest”: nie mastd::future
, ale zobacz wykład Seana Parenta „Lepszy kod: współbieżność” lub moje „Futures from Scratch”, aby poznać różne sposoby implementacji tego, jeśli nie masz nic przeciwko przepisaniu całego STL na początek. :) Kluczowym terminem wyszukiwania jest „anulowanie”.Problem polega na tym, że możesz otrzymać wiele wyjątków z wielu wątków, ponieważ każdy może się nie powieść, być może z różnych powodów.
Zakładam, że główny wątek w jakiś sposób czeka na zakończenie wątków, aby pobrać wyniki lub regularnie sprawdza postęp innych wątków, a dostęp do udostępnionych danych jest zsynchronizowany.
Proste rozwiązanie
Prostym rozwiązaniem byłoby wyłapanie wszystkich wyjątków w każdym wątku i zapisanie ich we wspólnej zmiennej (w głównym wątku).
Po zakończeniu wszystkich wątków zdecyduj, co zrobić z wyjątkami. Oznacza to, że wszystkie inne wątki kontynuowały przetwarzanie, co być może nie jest tym, czego chcesz.
Kompleksowe rozwiązanie
Bardziej złożonym rozwiązaniem jest sprawdzenie każdego wątku w strategicznych punktach ich wykonania, jeśli wyjątek został wyrzucony z innego wątku.
Jeśli wątek zgłasza wyjątek, jest przechwytywany przed wyjściem z wątku, obiekt wyjątku jest kopiowany do jakiegoś kontenera w głównym wątku (jak w prostym rozwiązaniu), a część współdzielonej zmiennej boolowskiej jest ustawiana na wartość true.
A kiedy inny wątek testuje tę wartość logiczną, widzi, że wykonanie ma zostać przerwane i przerywa wykonywanie w wdzięczny sposób.
Po przerwaniu wszystkich wątków główny wątek może obsłużyć wyjątek w razie potrzeby.
źródło
Wyjątek zgłoszony z wątku nie będzie możliwy do przechwycenia w wątku nadrzędnym. Wątki mają różne konteksty i stosy, a generalnie wątek nadrzędny nie musi tam pozostać i czekać na zakończenie działania elementów podrzędnych, aby mógł przechwycić ich wyjątki. Po prostu nie ma miejsca w kodzie na ten haczyk:
Będziesz musiał przechwycić wyjątki w każdym wątku i zinterpretować stan wyjścia z wątków w głównym wątku, aby ponownie wyrzucić wszystkie wyjątki, których możesz potrzebować.
Przy okazji, w przypadku braku catch w wątku jest to specyficzne dla implementacji, czy rozwijanie stosu będzie w ogóle wykonywane, tj. Destruktory zmiennych automatycznych mogą nie zostać wywołane nawet przed wywołaniem terminate. Niektóre kompilatory to robią, ale nie jest to wymagane.
źródło
Czy możesz serializować wyjątek w wątku roboczym, przesłać go z powrotem do wątku głównego, zdeserializować i ponownie zgłosić? Spodziewam się, że aby to zadziałało, wszystkie wyjątki musiałyby pochodzić z tej samej klasy (lub przynajmniej z małego zestawu klas z instrukcją switch ponownie). Poza tym nie jestem pewien, czy można je serializować, po prostu głośno myślę.
źródło
W istocie nie ma dobrego i ogólnego sposobu przesyłania wyjątków z jednego wątku do drugiego.
Jeśli, tak jak powinno, wszystkie twoje wyjątki pochodzą ze std :: wyjątek, możesz mieć ogólny chwyt wyjątków najwyższego poziomu, który w jakiś sposób wyśle wyjątek do głównego wątku, gdzie zostanie ponownie wyrzucony. Problem polega na tym, że tracisz punkt rzucania wyjątku. Prawdopodobnie możesz napisać kod zależny od kompilatora, aby uzyskać te informacje i przesłać je.
Jeśli nie wszystkie twoje wyjątki dziedziczą std :: wyjątek, to masz kłopoty i musisz napisać wiele haczyków najwyższego poziomu w swoim wątku ... ale rozwiązanie nadal działa.
źródło
Będziesz musiał wykonać ogólny catch dla wszystkich wyjątków w procesie roboczym (w tym wyjątków innych niż standardowe, takie jak naruszenia dostępu) i wysłać wiadomość z wątku roboczego (przypuszczam, że masz jakiś rodzaj wiadomości?) Do kontrolującego wątek, zawierający wskaźnik na żywo do wyjątku, i ponownie wyślij tam, tworząc kopię wyjątku. Następnie pracownik może uwolnić oryginalny przedmiot i wyjść.
źródło
Zobacz http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html . Możliwe jest również napisanie funkcji opakowującej dowolnej funkcji, którą wywołujesz, aby dołączyć do wątku podrzędnego, która automatycznie ponownie wyrzuca (za pomocą boost :: rethrow_exception) każdy wyjątek emitowany przez wątek potomny.
źródło