Kiedy należy używać std :: thread :: detach?

140

Czasami muszę użyć, std::threadaby przyspieszyć moją aplikację. Wiem też, że join()czeka, aż wątek się zakończy. Łatwo to zrozumieć, ale jaka jest różnica między dzwonieniem detach()a nie dzwonieniem?

Pomyślałem, że bez detach()wątku metoda wątku będzie działać niezależnie od wątku.

Nie odłączanie:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called without detach");
    });

    //some code here
}

Dzwonienie z odłączaniem:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called with detach");
    });

    t.detach();

    //some code here
}
Jinbom Heo
źródło
1
możliwy duplikat wątków odłączonych i łączonych POSIX
n. zaimki m.
Zarówno wątki, jak stdi boostwątki mają detachi są joinwzorowane na wątkach POSIX.
n. zaimki m.

Odpowiedzi:

149

W destruktorze std::thread, std::terminatejest wywoływane, jeśli:

  • wątek nie został połączony (z t.join())
  • i też nie został odłączony (z t.detach())

W związku z tym zawsze należy utworzyć wątek joinlub detachwątek, zanim przepływy wykonania dotrą do destruktora.


Gdy program się kończy (tj. mainZwraca), pozostałe odłączone wątki wykonywane w tle nie są oczekiwane; zamiast tego ich wykonanie zostaje zawieszone, a ich obiekty lokalne dla wątków zniszczone.

Co najważniejsze, oznacza to, że stos tych wątków nie jest rozwijany, a zatem niektóre destruktory nie są wykonywane. W zależności od działań, jakie te destruktory miały podjąć, może to być tak zła sytuacja, jak gdyby program się zawiesił lub został zabity. Miejmy nadzieję, że system operacyjny zwolni blokady plików itp., Ale mogłeś uszkodzić pamięć współdzieloną, częściowo zapisane pliki i tym podobne.


Więc powinieneś użyć joinlub detach?

  • Posługiwać się join
  • Chyba trzeba mieć większą elastyczność i są gotowi zapewnić mechanizm synchronizacji, aby czekać na zakończenie wątku na własną rękę , w takim przypadku można użyćdetach
Matthieu M.
źródło
Gdybym zadzwonił do pthread_exit (NULL); w main () wtedy exit () nie zostałoby wywołane z main () i dlatego program będzie kontynuował wykonywanie aż do zakończenia wszystkich odłączonych wątków. Następnie zostanie wywołane exit ().
Southerton
1
@Matthieu, dlaczego nie możemy dołączyć do destruktora std :: thread?
john smith
2
@johnsmith: Doskonałe pytanie! Co się stanie, gdy dołączysz? Czekasz, aż wątek się zakończy. Jeśli zostanie zgłoszony wyjątek, destruktory są wykonywane ... i nagle propagacja wyjątku zostaje zawieszona do momentu zakończenia wątku. Jest wiele powodów, dla których tego nie robi, zwłaszcza jeśli oczekuje na wejście z aktualnie zawieszonego wątku! Dlatego projektanci zdecydowali się uczynić to wyraźnym wyborem, zamiast wybrać kontrowersyjną wartość domyślną.
Matthieu M.,
@Matthieu Myślę, że masz na myśli wywołanie join () przed osiągnięciem destruktora std :: thread. Czy możesz (i powinieneś?) Dołączyć do destruktora klasy obejmującej?
Jose Quinteiro
4
@JoseQuinteiro: Rzeczywiście, w przeciwieństwie do innych środków, zaleca się nie przyłączyć z destructor. Problem polega na tym, że dołączanie nie kończy wątku, po prostu czeka na jego zakończenie, aw przeciwieństwie do tego, że masz sygnał powodujący zakończenie wątku, możesz czekać przez długi czas ... blokując bieżący wątek, którego stos jest rozwijany i uniemożliwia przerwanie tego aktualnego wątku, blokując w ten sposób wątek czekający na niego itp. Więc jeśli nie jesteś pewien , że możesz zatrzymać dany wątek w rozsądnym czasie, najlepiej nie czekać na to w destruktorze.
Matthieu M.
25

Powinieneś zadzwonić, detachjeśli nie zamierzasz czekać na zakończenie wątku, joinale zamiast tego wątek będzie po prostu działał, aż zostanie zakończony, a następnie zakończy działanie bez czekania na główny wątek.

detachzasadniczo uwolni zasoby potrzebne do wdrożenia join.

Jest to błąd krytyczny, jeśli obiekt nitki kończy swoje życie i ani joinnie detachzostał wywołany; w tym przypadku terminatejest wywoływana.

6502
źródło
12
Należy wspomnieć, że terminate jest wywoływany w destruktorze, jeśli wątek nie został ani przyłączony, ani odłączony .
nosid
11

Po odłączeniu wątku oznacza to, że nie musisz tego robić join()przed wyjściem main().

Biblioteka wątków będzie faktycznie czekać na każdy taki wątek poniżej głównego , ale nie powinieneś się tym przejmować.

detach()przydaje się głównie wtedy, gdy masz zadanie do wykonania w tle, ale nie przejmujesz się jego wykonaniem. Tak jest zwykle w przypadku niektórych bibliotek. Mogą po cichu utworzyć wątek roboczy w tle i odłączyć go, abyś nawet tego nie zauważył.

GreenScape
źródło
To nie odpowiada na pytanie. Odpowiedź zasadniczo brzmi: „odłączasz się, kiedy się odłączasz”.
rubenvb
7

Ta odpowiedź ma na celu udzielenie odpowiedzi na pytanie w tytule, a nie wyjaśnienie różnicy między joini detach. Więc kiedy powinienstd::thread::detach należy użyć?

W prawidłowo utrzymywanym C ++ kod w std::thread::detachogóle nie powinien być używany. Programista musi upewnić się, że wszystkie utworzone wątki z wdziękiem kończą pracę, zwalniając wszystkie nabyte zasoby i wykonując inne niezbędne działania czyszczące. Oznacza to, że rezygnacja z własności wątków przez wywołanie detachnie jest opcją, a zatemjoin powinna być używana we wszystkich scenariuszach.

Jednak niektóre aplikacje opierają się na starych i często niezbyt dobrze zaprojektowanych i obsługiwanych interfejsach API, które mogą zawierać funkcje blokujące na czas nieokreślony. Przenoszenie wywołań tych funkcji do dedykowanego wątku w celu uniknięcia blokowania innych rzeczy jest powszechną praktyką. Nie ma sposobu, aby taki wątek wyszedł z wdziękiem, więc użycie joinspowoduje po prostu zablokowanie głównego wątku. To sytuacja, w której użycie detachbyłoby mniej złą alternatywą dla, powiedzmy, przydzielania threadobiektu z dynamicznym czasem przechowywania, a następnie celowego wycieku.

#include <LegacyApi.hpp>
#include <thread>

auto LegacyApiThreadEntry(void)
{
    auto result{NastyBlockingFunction()};
    // do something...
}

int main()
{
    ::std::thread legacy_api_thread{&LegacyApiThreadEntry};
    // do something...
    legacy_api_thread.detach();
    return 0;
}
user7860670
źródło
1

Według cppreference.com :

Oddziela wątek wykonania od obiektu wątku, umożliwiając niezależne kontynuowanie wykonywania. Wszelkie przydzielone zasoby zostaną zwolnione po zamknięciu wątku.

Po wywołaniu detach *thisnie jest już właścicielem żadnego wątku.

Na przykład:

  std::thread my_thread([&](){XXXX});
  my_thread.detach();

Zwróć uwagę na zmienną lokalną:, my_threadgdy czas życia my_threadsię skończył, destruktor std::threadzostanie wywołany istd::terminate() zostanie wywołany w ramach destruktora.

Ale jeśli używasz detach(), nie powinieneś my_threadjuż używać , nawet jeśli żywotność my_threadsię skończy, nic się nie stanie z nowym wątkiem.

DinoStray
źródło
OK, cofam to, co powiedziałem przed chwilą. @ TobySpeight
DinoStray
1
Zwróć uwagę, że jeśli używasz &w przechwytywaniu lambda, używasz zmiennych otaczającego zakresu przez odwołanie - więc lepiej upewnij się, że czas życia każdego odwołania jest dłuższy niż czas życia wątku.
DavidJ