Wanneer moet ik std::thread::detach gebruiken?

Soms moet ik std::threadgebruiken om mijn aanvraag te versnellen. Ik weet ook dat join()wacht tot een thread is voltooid. Dit is gemakkelijk te begrijpen, maar wat is het verschil tussen het aanroepen van detach()en het niet aanroepen?

Ik dacht dat zonder detach()de methode van de thread onafhankelijk van een thread zou werken.

Niet loskoppelen:

void Someclass::Somefunction() {
    //...
    std::thread t([ ] {
        printf("thread called without detach");
    });
    //some code here
}

Bellen met loskoppelen:

void Someclass::Somefunction() {
    //...
    std::thread t([ ] {
        printf("thread called with detach");
    });
    t.detach();
    //some code here
}

Antwoord 1, autoriteit 100%

In de destructor van std::threadwordt std::terminateaangeroepen als:

  • de thread is niet toegevoegd (met t.join())
  • en was ook niet onthecht (met t.detach())

Je moet dus altijd een thread joinof detachvoordat de uitvoeringsstromen de destructor bereiken.


Als een programma wordt beëindigd (dwz mainkeert terug) wordt niet gewacht op de resterende losse threads die op de achtergrond worden uitgevoerd; in plaats daarvan wordt hun uitvoering opgeschort en hun thread-lokale objecten vernietigd.

Cruciaal, dit betekent dat de stapel van die threads niet wordt afgewikkelden dat sommige destructors dus niet worden uitgevoerd. Afhankelijk van de acties die die destructors moesten ondernemen, zou dit een even erge situatie kunnen zijn alsof het programma was gecrasht of was gedood. Hopelijk zal het besturingssysteem de vergrendelingen op bestanden, enz. vrijgeven… maar je zou het gedeelde geheugen, halfgeschreven bestanden en dergelijke kunnen hebben beschadigd.


Dus, moet je joinof detachgebruiken?

  • Gebruik join
  • Tenzij je meer flexibiliteit nodig hebt EN bereid bent om een ​​synchronisatiemechanisme te bieden om op je eigente wachten tot de thread is voltooid, in welk geval je detach

Antwoord 2, autoriteit 20%

Je moet detachaanroepen als je niet wilt wachten tot de thread is voltooid met joinmaar de thread in plaats daarvan gewoon blijft draaien totdat het klaar is en dan wordt beëindigd zonder dat de spawner-thread er specifiek op wacht; bijv.

std::thread(func).detach(); // It's done when it's done

detachzal in principe de middelen vrijgeven die nodig zijn om jointe kunnen implementeren.

Het is een fatale fout als een thread-object zijn leven beëindigt en noch joinnoch detachis aangeroepen; in dit geval wordt terminateaangeroepen.


Antwoord 3, autoriteit 10%

Dit antwoord is bedoeld om de vraag in de titel te beantwoorden, in plaats van het verschil tussen joinen detachuit te leggen. Dus wanneer moet std::thread::detachworden gebruikt?

In goed onderhouden C++-code mag std::thread::detachhelemaal niet worden gebruikt. De programmeur moet ervoor zorgen dat alle gemaakte threads netjes worden afgesloten, alle verworven bronnen vrijgeven en andere noodzakelijke opruimacties uitvoeren. Dit houdt in dat het opgeven van eigendom van threads door detachaan te roepen geen optie is en daarom moet joinin alle scenario’s worden gebruikt.

Sommige applicaties zijn echter afhankelijk van oude en vaak niet goed ontworpen en ondersteunde API’s die voor onbepaalde tijd blokkerende functies kunnen bevatten. Het verplaatsen van aanroepingen van deze functies naar een speciale thread om te voorkomen dat andere dingen worden geblokkeerd, is een gangbare praktijk. Er is geen manier om zo’n thread op een elegante manier af te sluiten, dus het gebruik van joinzal alleen maar leiden tot blokkering van de primaire thread. Dat is een situatie waarin het gebruik van detacheen minder slecht alternatief zou zijn voor, laten we zeggen, het toewijzen van een thread-object met een dynamische opslagduur en het vervolgens met opzet te lekken.

#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;
}

Antwoord 4, autoriteit 7%

Als je een thread loskoppelt, betekent dit dat je niet join()hoeft te gebruiken voordat je main()verlaat.

De threadbibliotheek wacht eigenlijk op elk van deze threads below-main, maar u zou er niets om moeten geven.

detach()is vooral handig als je een taak hebt die op de achtergrond moet worden uitgevoerd, maar de uitvoering niet belangrijk vindt. Dit is meestal het geval voor sommige bibliotheken. Ze kunnen stilletjes een achtergrondwerkthread maken en deze loskoppelen zodat je het niet eens merkt.


Antwoord 5, autoriteit 2%

Volgens cppreference.com:

Scheidt de uitvoeringsthread van het thread-object, waardoor
uitvoering zelfstandig voort te zetten. Alle toegewezen middelen worden
vrijgemaakt zodra de thread wordt afgesloten.

Na het aanroepen van detach *thisbezit geen enkele thread meer.

Bijvoorbeeld:

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

Let op de lokale variabele: my_thread, terwijl de levensduur van my_threadvoorbij is, wordt de destructor van std::threadaangeroepen, en std::terminate()wordt binnen de destructor aangeroepen.

Maar als u detach()gebruikt, moet u my_threadniet meer gebruiken, zelfs als de levensduur van my_threadvoorbij is, zal niets gebeurt er met de nieuwe thread.


Antwoord 6

Misschien is het een goed idee om te herhalen wat in een van de bovenstaande antwoorden is genoemd: wanneer de hoofdfunctie is voltooid en de hoofdthread wordt gesloten, worden alle spawn-threads beëindigd of onderbroken. Dus als u vertrouwt op detach om een ​​achtergrondthread te laten blijven draaien nadat de hoofdthread is afgesloten, staat u een verrassing te wachten. Probeer het volgende om het effect te zien. Als u de laatste slaapoproep verwijdert, wordt het uitvoerbestand gemaakt en naar fine geschreven. Anders niet:

#include <mutex>
#include <thread>
#include <iostream>
#include <fstream>
#include <array>
#include <chrono>
using Ms = std::chrono::milliseconds;
std::once_flag oflag;
std::mutex mx;
std::mutex printMx;
int globalCount{};
std::ofstream *logfile;
void do_one_time_task() {
    //printMx.lock();
    //std::cout<<"I am in thread with thread id: "<< std::this_thread::get_id() << std::endl;
    //printMx.unlock();
    std::call_once(oflag, [&]() {
    //  std::cout << "Called once by thread: " << std::this_thread::get_id() << std::endl; 
    //  std::cout<<"Initialized globalCount to 3\n";
        globalCount = 3;
        logfile = new std::ofstream("testlog.txt");
        //logfile.open("testlog.txt");
        });
    std::this_thread::sleep_for(Ms(100));
    // some more here
    for(int i=0; i<10; ++i){    
        mx.lock();
        ++globalCount;
        *logfile << "thread: "<< std::this_thread::get_id() <<", globalCount = " << globalCount << std::endl;
        std::this_thread::sleep_for(Ms(50));
        mx.unlock();
        std::this_thread::sleep_for(Ms(2));
    }
    std::this_thread::sleep_for(Ms(2000));
    std::call_once(oflag, [&]() {
        //std::cout << "Called once by thread: " << std::this_thread::get_id() << std::endl;
        //std::cout << "closing logfile:\n";
        logfile->close();
        });
}
int main()
{
    std::array<std::thread, 5> thArray;
    for (int i = 0; i < 5; ++i)
        thArray[i] = std::thread(do_one_time_task);
    for (int i = 0; i < 5; ++i)
        thArray[i].detach();
    //std::this_thread::sleep_for(Ms(5000));
    std::cout << "Main: globalCount = " << globalCount << std::endl;
    return 0;
}

Other episodes