Quando devo usar std :: thread :: desanexar?

139

Em algum momento eu tenho que usar std::threadpara acelerar meu aplicativo. Eu também sei join()espera até que um segmento seja concluído. Isso é fácil de entender, mas qual é a diferença entre ligar detach()e não ligar?

Eu pensei que detach(), sem , o método do thread funcionará usando um thread de forma independente.

Não desanexando:

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

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

    //some code here
}

Ligando com desanexação:

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

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

    t.detach();

    //some code here
}
Jinbom Heo
fonte
1
possível duplicação de threads POSIX desanexados vs. ingressáveis
n. 'pronomes' m.
Ambos stde boosttópicos têm detache joinmodelados de perto depois de threads POSIX.
n. 'pronomes' m.

Respostas:

149

No destruidor de std::thread, std::terminateé chamado se:

  • o encadeamento não foi unido (com t.join())
  • e também não foi desanexado (com t.detach())

Portanto, você sempre deve ter um joinou detachum encadeamento antes que os fluxos de execução cheguem ao destruidor.


Quando um programa termina (ou seja, mainretorna), os demais segmentos desanexados em execução em segundo plano não são esperados; em vez disso, sua execução é suspensa e seus objetos locais de thread destruídos.

Fundamentalmente, isso significa que a pilha desses threads não é desenrolada e, portanto, alguns destruidores não são executados. Dependendo das ações que esses destruidores deveriam empreender, essa poderia ser uma situação tão ruim como se o programa tivesse travado ou tivesse sido morto. Esperamos que o sistema operacional libere os bloqueios nos arquivos, etc ... mas você pode ter corrompido a memória compartilhada, os arquivos semi-escritos e similares.


Então, você deve usar joinou detach?

  • Usar join
  • A menos que você precise ter mais flexibilidade E esteja disposto a fornecer um mecanismo de sincronização para aguardar a conclusão do encadeamento por conta própria , nesse caso, você poderá usardetach
Matthieu M.
fonte
Se eu chamar pthread_exit (NULL); em main (), então exit () não seria chamado de main () e, portanto, o programa continuará a execução até que todos os threads desanexados sejam concluídos. Então exit () seria chamado.
Southerton
1
@ Matthieu, por que não podemos nos juntar ao destruidor do std :: thread?
31816 John Smith Smith
2
@ johnsmith: Uma excelente pergunta! O que acontece quando você entra? Você espera até que o segmento seja concluído. Se uma exceção for lançada, os destruidores serão executados ... e de repente a propagação da sua exceção será suspensa até que o encadeamento termine. Há muitas razões para isso, principalmente se estiver aguardando a entrada do encadeamento atualmente suspenso! Portanto, os designers escolheram fazer uma escolha explícita, em vez de escolher um padrão controverso.
Matthieu M.
@ Matthieu Eu acho que você quer dizer chamar join () antes que o destruidor std :: thread seja atingido. Você pode (e deve?) Ingressar () no destruidor de uma classe anexa?
Jose Quinteiro 23/11
4
@ JoseQuinteiro: Na verdade, ao contrário de outros recursos, é aconselhável não participar de um destruidor. O problema é que a união não termina um encadeamento, apenas espera que ele termine e, ao contrário de um sinal no local para fazer com que o encadeamento seja encerrado, você pode estar esperando por um longo tempo ... bloqueando o encadeamento atual cuja pilha está sendo desenrolado e impedindo que esse encadeamento atual seja encerrado, bloqueando o encadeamento que o espera, etc ... Portanto, a menos que você tenha certeza de que pode parar um determinado encadeamento em um período de tempo razoável, é melhor não esperar por em um destruidor.
precisa
25

Você deve ligar detachse não quiser esperar que o encadeamento seja concluído, joinmas o encadeamento continuará sendo executado até que esteja pronto e será encerrado sem que o encadeamento principal o aguarde especificamente.

detachbasicamente liberará os recursos necessários para poder implementar join.

É um erro fatal se um objeto de encadeamento termina sua vida e nem joinnem detachfoi chamado; neste caso terminateé invocado.

6502
fonte
12
Você deve mencionar que esse término é chamado no destruidor, se o encadeamento não foi unido nem desanexado .
Nosid 2/14
11

Quando você desanexa o encadeamento, significa que você não precisa fazer join()isso antes de sair main().

A biblioteca de encadeamentos realmente aguardará cada um desses encadeamentos abaixo do principal , mas você não deve se preocupar com isso.

detach()é útil principalmente quando você tem uma tarefa que precisa ser executada em segundo plano, mas não se importa com a execução. Geralmente, esse é o caso de algumas bibliotecas. Eles podem criar silenciosamente um thread de trabalho em segundo plano e desanexá-lo para que você nem perceba.

GreenScape
fonte
Isso não responde à pergunta. A resposta basicamente afirma "você se desconecta quando se desconecta".
rubenvb
7

Esta resposta visa responder à pergunta no título, em vez de explicar a diferença entre joine detach. Então, quando deve std::thread::detachser usado?

No código C ++ mantido adequadamente std::thread::detach, não deve ser usado. O programador deve garantir que todos os threads criados saiam normalmente, liberando todos os recursos adquiridos e executando outras ações de limpeza necessárias. Isso implica que renunciar à propriedade dos encadeamentos invocando detachnão é uma opção e, portanto, joindeve ser usado em todos os cenários.

No entanto, alguns aplicativos contam com APIs antigas e muitas vezes mal projetadas e suportadas, que podem conter funções de bloqueio indefinidamente. Mover invocações dessas funções para um thread dedicado para evitar o bloqueio de outras coisas é uma prática comum. Não há como fazer com que esse encadeamento saia normalmente, portanto, o uso de joinapenas levará ao bloqueio do encadeamento primário. Essa é uma situação em que o uso detachseria uma alternativa menos ruim para, por exemplo, alocar um threadobjeto com duração de armazenamento dinâmico e vazá-lo de 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;
}
user7860670
fonte
1

De acordo com cppreference.com :

Separa o encadeamento de execução do objeto de encadeamento, permitindo que a execução continue independentemente. Quaisquer recursos alocados serão liberados assim que o encadeamento terminar.

Após chamar desanexar, *thisnão possui mais nenhum segmento.

Por exemplo:

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

Observe a variável local: my_threadenquanto o tempo de vida útil my_threadterminar, o destruidor de std::threadserá chamado e std::terminate()será chamado dentro do destruidor.

Mas se você usar detach(), não deverá my_threadmais usá-lo , mesmo que a vida útil my_threadtenha acabado, nada acontecerá com o novo encadeamento.

DinoStray
fonte
OK, eu retiro o que eu disse há pouco @ TobySpeight.
DinoStray
1
Observe que, se você usar &na captura lambda, estará usando as variáveis ​​do escopo anexado por referência - para garantir que o tempo de vida de qualquer referência que você refira seja maior que o tempo de vida do encadeamento.
precisa saber é o seguinte