¿Por qué se devuelve el destructor de un futuro del locking `std :: async`?

Al intentar responder a otra pregunta de Stackoverflow , me di cuenta de que este simple fragmento de C ++ 11 está implícitamente bloqueando el hilo de llamada:

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

Para mí, esto habría parecido la manera canónica de C ++ 11 de iniciar una tarea de forma asíncrona sin preocuparme por el resultado. En su lugar, uno tiene que crear y separar explícitamente un hilo (ver respuesta a la pregunta mencionada) para lograr esto.

Así que aquí está mi pregunta: ¿Hay alguna razón con respecto a la seguridad / corrección que el destructor de std::future tiene que estar bloqueando? ¿No sería suficiente si bloquea en get only y de lo contrario, si no estoy interesado en el valor de retorno o excepción, es simplemente fuego y olvidar?

Bloqueo de destructores de futuros devueltos por std :: async y de hilos: ese es un tema controvertido. La siguiente lista de documentos en orden cronológico refleja algunas de las discusiones de los miembros del comité:

  • N2802: una petición para reconsiderar la separación sobre la destrucción de objetos de hilo por Hans Boehm
  • N3630: async, ~ future, y ~ thread (Revisión 1) por Herb Sutter
  • N3636: ~ thread debería unirse por Herb Sutter
  • N3637: async y ~ future (Revisión 3) por Herb Sutter, Chandler Carruth, Niklas Gustafsson
  • N3679: Async () futuros destructores deben esperar por Hans Boehm
  • N3773: async y ~ future (Revisión 4) por Herb Sutter, Chandler Carruth, Niklas Gustafsson
  • N3776: Redacción para ~ future por Herb Sutter
  • N3777: Redacción para la desaprobación asíncrona por Herb Sutter

Aunque hubo mucha discusión, no hay cambios planeados para C ++ 14 con respecto al comportamiento de locking de los destructores de std :: future y std :: thread .

Con respecto a su pregunta, el documento más interesante es probablemente el segundo de Hans Boehm. Cito algunas partes para responder a tu pregunta.

N3679: los destructores futuros de Async () deben esperar

[..] Los futuros devueltos por async() con una política de inicio async esperan en su destructor para que el estado compartido asociado esté listo. Esto evita una situación en la que el hilo asociado continúa ejecutándose y ya no hay un medio para esperar que se complete porque el futuro asociado ha sido destruido. Sin esfuerzos heroicos por esperar de otra manera la finalización, un hilo de “fuga” puede seguir funcionando más allá de la vida útil de los objetos de los que depende.

[Ejemplo]

El resultado final es probable que sea un “golpe de memoria” de hilos cruzados. Este problema, por supuesto, se evita si get() o wait() se llama […] antes de que [los futuros] se destruyan. La dificultad [..] es que una excepción inesperada puede hacer que ese código sea anulado. Por lo tanto, se necesita algún tipo de protector de scope para garantizar la seguridad. Si el progtwigdor olvida agregar el protector de scope, parece probable que un atacante podría generar, por ejemplo, una excepción bad_alloc en un momento oportuno para aprovechar la supervisión y sobrescribir una stack. También es posible controlar los datos utilizados para sobrescribir la stack y así obtener control sobre el proceso. Este es un error suficientemente sutil que, en nuestra experiencia, es probable que se pase por alto en un código real.

Actualización: El Informe de viaje de Michael Wong también contiene información interesante sobre los resultados de la reunión de septiembre de 2013:

La vista desde la reunión estándar de C ++, septiembre de 2013, parte 2 de 2.

Sobre el tema que los destructores asíncronos no deberían bloquear, dediqué una gran cantidad de discusión al respecto. […] La única posición que recibió un apoyo considerable fue [..] dando aviso de que los futuros destructores no se bloquearán, a menos que sean devueltos por asincrónicos, convirtiéndolo en la excepción notable. […] Después de una discusión significativa, la única parte que intentamos llevar era N3776, un bash de aclarar la posición que ~future y ~shared_future no bloquean, excepto posiblemente en presencia de async. Hubo un bash de emitir una depreciación en la línea de C. Deprecate asincrónico sin reemplazo. Esta moción en realidad fue casi presentada. Pero […] murió incluso antes de llegar a la mesa de operaciones.