Soms moet ik std::thread
gebruiken 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::thread
wordt std::terminate
aangeroepen als:
- de thread is niet toegevoegd (met
t.join()
) - en was ook niet onthecht (met
t.detach()
)
Je moet dus altijd een thread join
of detach
voordat de uitvoeringsstromen de destructor bereiken.
Als een programma wordt beëindigd (dwz main
keert 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 join
of detach
gebruiken?
- 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 detach
aanroepen als je niet wilt wachten tot de thread is voltooid met join
maar 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
detach
zal in principe de middelen vrijgeven die nodig zijn om join
te kunnen implementeren.
Het is een fatale fout als een thread-object zijn leven beëindigt en noch join
noch detach
is aangeroepen; in dit geval wordt terminate
aangeroepen.
Antwoord 3, autoriteit 10%
Dit antwoord is bedoeld om de vraag in de titel te beantwoorden, in plaats van het verschil tussen join
en detach
uit te leggen. Dus wanneer moet std::thread::detach
worden gebruikt?
In goed onderhouden C++-code mag std::thread::detach
helemaal 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 detach
aan te roepen geen optie is en daarom moet join
in 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 join
zal alleen maar leiden tot blokkering van de primaire thread. Dat is een situatie waarin het gebruik van detach
een 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
*this
bezit 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_thread
voorbij is, wordt de destructor van std::thread
aangeroepen, en std::terminate()
wordt binnen de destructor aangeroepen.
Maar als u detach()
gebruikt, moet u my_thread
niet meer gebruiken, zelfs als de levensduur van my_thread
voorbij 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;
}