¿Cuándo debo usar std :: thread :: detach?

140

En algún momento tengo que usar std::threadpara acelerar mi aplicación. También sé que join()espera hasta que se complete un hilo. Esto es fácil de entender, pero ¿cuál es la diferencia entre llamar detach()y no llamar?

Pensé que sin detach(), el método del hilo funcionará usando un hilo de forma independiente.

No se separa:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called without detach");
    });

    //some code here
}

Llamando con desprendimiento:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called with detach");
    });

    t.detach();

    //some code here
}
Jinbom Heo
fuente
1
posible duplicado de hilos POSIX separados vs. unibles
n. 'pronombres' m.
Tanto stdy boostlas discusiones han detachy joinmodelada de cerca después de los hilos POSIX.
n. 'pronombres' m.

Respuestas:

149

En el destructor de std::thread, std::terminatese llama si:

  • el hilo no estaba unido (con t.join())
  • y tampoco fue separado (con t.detach())

Por lo tanto, siempre debe joino detachun hilo antes de que los flujos de ejecución lleguen al destructor.


Cuando un programa finaliza (es decir, mainregresa), no se esperan los subprocesos separados que se ejecutan en segundo plano; en cambio, su ejecución se suspende y sus objetos locales de subprocesos se destruyen.

Crucialmente, esto significa que la pila de esos hilos no se desenrolla y, por lo tanto, algunos destructores no se ejecutan. Dependiendo de las acciones que se suponía que debían emprender esos destructores, esta podría ser una situación tan grave como si el programa se hubiera bloqueado o hubiera sido asesinado. Esperemos que el sistema operativo libere los bloqueos en los archivos, etc. pero podría haber corrompido la memoria compartida, los archivos a medio escribir y similares.


Entonces, ¿deberías usar joino detach?

  • Utilizar join
  • A menos que necesite tener más flexibilidad Y esté dispuesto a proporcionar un mecanismo de sincronización para esperar la finalización del subproceso por su cuenta , en cuyo caso puede usardetach
Matthieu M.
fuente
Si llamaría a pthread_exit (NULL); en main (), luego exit () no se llamaría desde main () y, por lo tanto, el programa continuará ejecutándose hasta que se completen todos los hilos separados. Entonces se llamaría a exit ().
southerton
1
@ Matthieu, ¿por qué no podemos unirnos en el destructor de std :: thread?
John Smith
2
@johnsmith: ¡Una excelente pregunta! ¿Qué pasa cuando te unes? Esperas hasta que se complete el hilo. Si se produce una excepción, se ejecutan los destructores ... y de repente la propagación de su excepción se suspende hasta que finaliza el hilo. ¡Hay muchas razones para que no lo haga, especialmente si está esperando la entrada del hilo actualmente suspendido! Por lo tanto, los diseñadores decidieron hacer una elección explícita, en lugar de elegir un incumplimiento controvertido.
Matthieu M.
@ Matthieu Creo que te refieres a call join () antes de que se alcance el destructor de std :: thread. ¿Puedes (y deberías) unir () el destructor de una clase que lo encierra?
José Quinteiro el
44
@JoseQuinteiro: En realidad, a diferencia de otros recursos, se recomienda no unirse desde un destructor. El problema es que la unión no finaliza un subproceso, simplemente espera a que termine, y a diferencia de que tiene una señal en su lugar para hacer que el subproceso finalice, puede estar esperando mucho tiempo ... bloqueando el subproceso actual cuya pila se desenrolla y evita que este subproceso actual finalice, bloqueando el subproceso que lo espera, etc. Por lo tanto, a menos que esté seguro de que puede detener un subproceso determinado en un período de tiempo razonable, es mejor no esperar en un destructor
Matthieu M. el
25

Debe llamar detachsi no va a esperar a que se complete joinel subproceso, pero el subproceso seguirá funcionando hasta que esté hecho y luego terminará sin que el subproceso principal lo espere específicamente.

detachbásicamente liberará los recursos necesarios para poder implementar join.

Es un error fatal si un objeto hilo termina su vida y ni jointampoco detachse ha llamado; En este caso terminatese invoca.

6502
fuente
12
Debe mencionar que se llama a terminate en el destructor, si el hilo no se ha unido ni separado .
nosid
11

Cuando desconecta el hilo, significa que no tiene que join()hacerlo antes de salir main().

La biblioteca de subprocesos realmente esperará cada uno de estos subprocesos debajo de main , pero no debería importarle.

detach()es principalmente útil cuando tiene una tarea que debe hacerse en segundo plano, pero no le importa su ejecución. Este suele ser un caso para algunas bibliotecas. Pueden crear silenciosamente un subproceso de trabajo en segundo plano y separarlo para que ni siquiera lo note.

GreenScape
fuente
Esto no responde la pregunta. La respuesta básicamente dice "te separas cuando te separas".
rubenvb
7

Esta respuesta tiene como objetivo responder la pregunta en el título, en lugar de explicar la diferencia entre joiny detach. Entonces, ¿cuándo debería std::thread::detachusarse?

En el código C ++ mantenido correctamente std::thread::detachno se debe utilizar en absoluto. El programador debe asegurarse de que todos los hilos creados salgan con gracia liberando todos los recursos adquiridos y realizando otras acciones de limpieza necesarias. Esto implica que renunciar a la propiedad de los hilos invocando detachno es una opción y, por joinlo tanto, debe usarse en todos los escenarios.

Sin embargo, algunas aplicaciones se basan en API antiguas, a menudo mal diseñadas y compatibles, que pueden contener funciones de bloqueo indefinido. Mover invocaciones de estas funciones a un hilo dedicado para evitar bloquear otras cosas es una práctica común. No hay forma de hacer que un hilo de este tipo salga con gracia, por lo que el uso de joineste solo conducirá al bloqueo del hilo primario. Esa es una situación en la que usar detachsería una alternativa menos malvada para, por ejemplo, asignar threadobjetos con una duración de almacenamiento dinámico y luego filtrarlos a propósito.

#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;
}
usuario7860670
fuente
1

De acuerdo con cppreference.com :

Separa el hilo de ejecución del objeto de hilo, permitiendo que la ejecución continúe independientemente. Cualquier recurso asignado se liberará una vez que salga el hilo.

Después de llamar a detach *thisya no posee ningún hilo.

Por ejemplo:

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

Observe la variable local: my_threadmientras my_threadfinaliza la vida útil de , std::threadse llamará al destructor de y std::terminate()se llamará dentro del destructor.

Pero si lo usa detach(), ya no debería usarlo my_thread, incluso si la vida útil de my_threadha terminado, no le pasará nada al nuevo hilo.

DinoStray
fuente
OK, retomo lo que acabo de decir. @ TobySpeight
DinoStray
1
Tenga en cuenta que si utiliza &en la captura lambda está utilizando las variables del ámbito de inclusión por referencia , por lo que es mejor asegurarse de que la vida útil de cualquiera de las referencias sea más larga que la vida útil de su hilo.
DavidJ