Perché il distruttore di un futuro è restituito dal blocco `std :: async`?

Quando ho provato a rispondere a un’altra domanda StackOverflow , mi sono reso conto che questo semplice snippet di C ++ 11 blocca implicitamente il thread chiamante:

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

Per me questo sarebbe sembrato il modo canonico di C ++ 11 per avviare un’attività in modo asincrono senza preoccuparsi del risultato. Invece, è necessario creare e staccare apparentemente un thread (vedere la risposta alla domanda citata) per ottenere questo risultato.

Quindi, ecco la mia domanda: c’è qualche ragione per quanto riguarda la sicurezza / correttezza che il distruttore di uno std::future deve bloccare? Non sarebbe sufficiente se blocchi solo per get e altrimenti, se non sono interessato al valore di ritorno o all’eccezione, è semplicemente il fuoco e dimenticare?

Blocco dei distruttori dei futures restituiti da std :: async e dei thread: è un argomento controverso. Il seguente elenco di documenti in ordine cronologico riflette alcune delle discussioni dei membri del comitato:

  • N2802: un appello a riconsiderare la distruzione per oggetti thread di Hans Boehm
  • N3630: async, ~ future e ~ thread (Revisione 1) di Herb Sutter
  • N3636: ~ thread dovrebbe unirsi con Herb Sutter
  • N3637: async e ~ future (Revisione 3) di Herb Sutter, Chandler Carruth, Niklas Gustafsson
  • N3679: Async () i futuri distruttori devono aspettare Hans Boehm
  • N3773: async e ~ future (Revisione 4) di Herb Sutter, Chandler Carruth, Niklas Gustafsson
  • N3776: Testo per ~ futuro di Herb Sutter
  • N3777: Testo per deprecazione asincrona di Herb Sutter

Sebbene ci sia stata molta discussione, non sono state pianificate modifiche per C ++ 14 riguardo al comportamento di blocco dei distruttori di std :: future e std :: thread .

Per quanto riguarda la tua domanda, il documento più interessante è probabilmente il secondo di Hans Boehm. Cito alcune parti per rispondere alla tua domanda.

N3679: Async () i futuri distruttori devono attendere

[..] I futures restituiti da async() con il criterio di avvio async attendono nel proprio distruttore che lo stato condiviso associato diventi pronto. Ciò impedisce una situazione in cui il thread associato continua a essere eseguito e non è più ansible attendere che si completi perché il futuro associato è stato distrutto. Senza sforzi eroici per aspettare altrimenti il ​​completamento, un tale thread “run-away” può continuare a scorrere oltre la durata degli oggetti da cui dipende.

[Esempio]

È probabile che il risultato finale sia uno “smash di memoria” cross-thread. Questo problema è ovviamente evitato se get() o wait() viene chiamato [..] prima che [i futures] vengano distrutti. La difficoltà [..] è che un’eccezione imprevista potrebbe causare il bypass del codice. Pertanto, per garantire la sicurezza è di solito necessaria una sorta di protezione per l’ambito. Se il programmatore si dimentica di aggiungere la protezione dell’oscilloscopio, sembra probabile che un utente malintenzionato possa generare ad esempio un’eccezione bad_alloc in un punto opportuno per sfruttare la supervisione e causare la sovrascrittura di uno stack. Potrebbe essere ansible anche controllare i dati usati per sovrascrivere lo stack e ottenere così il controllo del processo. Questo è un errore sufficientemente sottile che, nella nostra esperienza, è probabile che venga trascurato nel codice reale.

Aggiornamento: il rapporto di viaggio di Michael Wong contiene anche alcune informazioni interessanti sui risultati dell’incontro nel settembre 2013:

The View from the C ++ Standard meeting September 2013 Part 2 of 2.

Sulla questione che i distruttori asincroni non dovrebbero bloccare abbiamo dedicato una grande quantità di discussioni al riguardo. [..] L’unica posizione che ha ricevuto un notevole supporto è stata [..] fornendo consulenza che i futuri distruttori non bloccheranno, a meno che non siano restituiti da asincrona, rendendola l’eccezione degna di nota. [..] Dopo una discussione significativa, l’unica parte che abbiamo cercato di portare era N3776, un tentativo di chiarire la posizione che ~future e ~shared_future non bloccano tranne forse in presenza di asincrono. C’è stato un tentativo di emettere una dichiarazione su una riga di C. Deprecate async senza sostituzione. In realtà, questa proposta fu quasi avanzata. Ma [..] morì anche prima che raggiungesse il tavolo operatorio.