Waarom wordt de destructor van een toekomst geretourneerd door `std::async` blokkering?

Bij het beantwoorden van een andere Stackoverflow-vraag, ik realiseerde me dat dit eenvoudige C++11-fragment impliciet de aanroepende thread blokkeert:

std::async(std::launch::async, run_async_task)

Voor mij zou dit de canonieke C++11-manier zijn geweest om een ​​taak asynchroon te starten zonder om het resultaat te geven. In plaats daarvan moet men blijkbaar expliciet een thread maken en loskoppelen (zie antwoordop genoemde vraag) om dit te bereiken.

Dus hier is mijn vraag: Is er een reden met betrekking tot veiligheid/correctheid dat de destructor van een std::futuremoet blokkeren? Zou het niet voldoende zijn als het alleen blokkeert op geten anders, als ik niet geïnteresseerd ben in de retourwaarde of uitzondering, is het gewoon vuur en vergeet?


Antwoord 1, autoriteit 100%

Blokkeren van destructors van futures geretourneerd door std::asyncen van threads: dat is een controversieel onderwerp. De volgende lijst van artikelen in chronologische volgorde geeft enkele van de discussies weer door de leden van de commissie:

Hoewel er veel discussie was, zijn er geen wijzigingen gepland voor C++14 met betrekking tot het blokkeergedrag van de destructors van std::futureen std::thread.

Wat uw vraag betreft, het meest interessante artikel is waarschijnlijk het tweede van Hans Boehm. Ik citeer enkele delen om je vraag te beantwoorden.

N3679: Async() toekomstige destructors moeten wachten

[..] Futures geretourneerd door async()met asyncstartbeleid wachten in hun destructor totdat de bijbehorende gedeelde status gereed is. Dit voorkomt een situatie waarin de bijbehorende thread blijft lopen, en er is geen middel meer om te wachten tot deze is voltooid omdat de bijbehorende toekomst is vernietigd. Zonder heroïsche inspanningen om anders op voltooiing te wachten, kan zo’n “op hol geslagen” thread doorgaan na de levensduur van de objecten waarvan het afhankelijk is.

[Voorbeeld]

Het eindresultaat is waarschijnlijk een cross-thread “memory smash”. Dit probleem wordt natuurlijk vermeden als get()of wait()wordt aangeroepen [..] voordat ze [de futures] worden vernietigd. De moeilijkheid [..] is dat een onverwachte uitzondering ervoor kan zorgen dat die code wordt omzeild. Er is dus meestal een soort scope guard nodig om de veiligheid te waarborgen. Als de programmeur vergeet de scope guard toe te voegen, lijkt het waarschijnlijk dat een aanvaller b.v. een bad_alloc-uitzondering op een geschikt moment om te profiteren van het overzicht, en ervoor te zorgen dat een stapel wordt overschreven. Het kan mogelijk zijn om ook de gegevens te controleren die worden gebruikt om de stapel te overschrijven, en zo controle over het proces te krijgen. Dit is een voldoende subtiele fout die, naar onze ervaring, waarschijnlijk over het hoofd wordt gezien in echte code.

Update:Michael Wong’s Reisrapportbevat ook enkele interessante informatie over de resultaten van de bijeenkomst in september 2013:

Het uitzicht vanaf de C++ Standard meeting September 2013 Deel 2 van 2.

Over de kwestie dat asynchrone destructors niet moeten worden geblokkeerd, hebben we er veel over gediscussieerd. [..] Het enige standpunt dat aanzienlijke steun kreeg, was [..] het geven van advies dat toekomstige destructors niet zullen blokkeren, tenzij ze terugkeren van async, waardoor het de opmerkelijke uitzondering is. [..] Na veel discussie was het enige deel dat we probeerden te dragen N3776, een poging om de positie te verduidelijken die ~futureen ~shared_futureniet blokkeren, behalve mogelijk in de aanwezigheid van asynchrone. Er is een poging gedaan om een ​​depreciatie uit te vaardigen in de trant van C. Deprecate async zonder vervanging. Deze motie was eigenlijk bijna ingediend. Maar [..] het stierf zelfs voordat het de operatietafel bereikte.

Other episodes