O que acontece com um segmento desconectado quando o main () sai?

Suponha que eu esteja iniciando um std::thread e, em seguida, detach() , então o thread continua executando mesmo que o std::thread que o representou, saia do escopo.

Suponha ainda que o programa não tenha um protocolo confiável para unir o encadeamento desanexado 1 , de modo que o encadeamento desanexado ainda seja executado quando o main() sair.

Não consigo encontrar nada no padrão (mais precisamente, no N3797 C ++ 14 draft), que descreve o que deve acontecer, nem 1.10 nem 30.3 contêm uma redação pertinente.

1 Outra questão, provavelmente equivalente, é: “um thread desanexado pode ser unido novamente”, porque qualquer que seja o protocolo para o qual você está inventando, a parte de sinalização teria que ser feita enquanto o thread ainda estava em execução e o planejador do SO decida colocar o encadeamento em repouso por uma hora logo após a sinalização ser executada sem que seja possível que a extremidade de recebimento detecte com segurança que a linha realmente terminou.

Se ficar sem o main() com os threads desconectados em execução for um comportamento indefinido, qualquer uso de std::thread::detach() é um comportamento indefinido, a menos que o thread principal nunca saia de 2 .

Assim, a execução de main() com encadeamentos desanexados em execução deve ter efeitos definidos . A questão é: onde (no padrão C ++ , não POSIX, não docs do SO, …) são esses efeitos definidos.

2 Um segmento desanexado não pode ser unido (no sentido de std::thread::join() ). Você pode esperar por resultados de threads desanexados (por exemplo, através de um futuro de std::packaged_task , ou por um semáforo de contagem ou um sinalizador e uma variável de condição), mas isso não garante que o segmento tenha terminado a execução . De fato, a menos que você coloque a parte de sinalização no destruidor do primeiro object automático do encadeamento, haverá , em geral, código (destruidores) que correm após o código de sinalização. Se o sistema operacional planejar o segmento principal para consumir o resultado e sair antes que o segmento desconectado conclua a execução dos referidos destruidores, o que o ^ Wis definido para acontecer?

   

A resposta para a pergunta original “o que acontece com um thread desanexado quando main() sai” é:

Ele continua em execução (porque o padrão não diz que está parado), e isso é bem definido, contanto que não atinja nenhuma variável (automática | thread_local) de outros threads nem objects estáticos.

Isto parece ser permitido para permitir gerenciadores de threads como objects estáticos (nota em [basic.start.term] / 4 diz isso, graças a @dyp para o ponteiro).

Os problemas surgem quando a destruição de objects estáticos termina, porque então a execução entra em um regime onde somente o código permitido em manipuladores de sinais pode executar ( [basic.start.term] / 1, primeira sentença ). Da biblioteca padrão C ++, isto é apenas a biblioteca ( [support.runtime] / 9, 2ª sentença ). Em particular, que – em geral – exclui condition_variable (sua implementação é definida se é salva para usar em um manipulador de sinal, porque não faz parte de ).

A menos que você tenha desenrolado sua pilha neste momento, é difícil ver como evitar um comportamento indefinido.

A resposta para a segunda pergunta “pode ​​desassociar os tópicos a serem unidos novamente” é:

Sim, com a família de funções *_at_thread_exit ( notify_all_at_thread_exit() , std::promise::set_value_at_thread_exit() , …).

Como observado na nota de rodapé [2] da questão, sinalizar uma variável de condição ou um semáforo ou um contador atômico não é suficiente para unir um segmento desanexado (no sentido de assegurar que o fim de sua execução tenha ocorrido – antes do recebimento de dita sinalização por um encadeamento em espera), porque, em geral, haverá mais código executado após, por exemplo, um notify_all() de uma variável de condição, em particular os destruidores de objects automáticos e locais de encadeamento.

Executar a sinalização como a última coisa que o encadeamento faz ( depois que os destruidores de objects locais e automáticos aconteceram ) é para o que a família de funções _at_thread_exit foi projetada.

Assim, para evitar um comportamento indefinido na ausência de garantias de implementação acima do que o padrão requer, você precisa (manualmente) unir um thread desanexado com uma function _at_thread_exit fazendo a sinalização ou fazer o thread desanexado executar somente o código que seria seguro para um manipulador de sinal também.

Destacando Threads

De acordo com std::thread::detach :

Separa o encadeamento da execução do object de encadeamento, permitindo que a execução continue de forma independente. Quaisquer resources alocados serão liberados quando o thread sair.

De pthread_detach :

A function pthread_detach () deve indicar à implementação que o armazenamento para o encadeamento pode ser recuperado quando esse encadeamento terminar. Se o encadeamento não tiver terminado, o pthread_detach () não fará com que ele termine. O efeito de várias chamadas pthread_detach () no mesmo segmento de destino não é especificado.

Desvincular os encadeamentos é principalmente para salvar resources, caso o aplicativo não precise esperar que um encadeamento seja concluído (por exemplo, daemons, que devem ser executados até a finalização do processo):

  1. Para liberar o identificador do lado do aplicativo: É possível deixar um object std::thread sair do escopo sem unir-se, o que normalmente leva a uma chamada para std::terminate() na destruição.
  2. Para permitir que o SO limpe automaticamente os resources específicos do encadeamento ( TCB ) assim que o encadeamento sair, porque explicitamente especificamos que não estamos interessados ​​em unir o encadeamento mais tarde, não é possível associar um encadeamento já desanexado.

Killing Threads

O comportamento na terminação do processo é o mesmo que o do segmento principal, que poderia pelo menos capturar alguns sinais. Se outros threads podem ou não manipular sinais não é tão importante, já que um poderia juntar ou terminar outros threads dentro da invocação do manipulador de sinal do thread principal. (Questão relacionada )

Como já foi dito, qualquer thread, independente ou não, irá morrer com seu processo na maioria dos sistemas operacionais . O processo em si pode ser terminado levantando um sinal, chamando exit() ou retornando da function principal. No entanto, o C ++ 11 não pode e não tenta definir o comportamento exato do SO subjacente, enquanto os desenvolvedores de uma VM Java podem certamente abstrair essas diferenças até certo ponto. AFAIK, modelos exóticos de processos e encadeamentos são normalmente encontrados em plataformas antigas (para as quais o C ++ 11 provavelmente não será portado) e vários sistemas embarcados, que poderiam ter uma implementação especial e / ou limitada da biblioteca de idiomas e também suporte limitado a idiomas.

Suporte de thread

Se os segmentos não forem suportados, std::thread::get_id() deve retornar um id inválido (padrão construído como std::thread::id ), pois há um processo simples, que não precisa de um object thread para ser executado e o construtor de um std::thread deve lançar um std::system_error . É assim que entendo o C ++ 11 em conjunto com os sistemas operacionais atuais. Se existe um sistema operacional com suporte a threads, que não gera um thread principal em seus processos, me avise.

Controlling Threads

Se for necessário manter o controle sobre um encadeamento para um desligamento adequado, é possível fazer isso usando primitivos de synchronization e / ou algum tipo de sinalizador. No entanto, neste caso, definir um sinalizador de desligamento seguido por uma junit é a maneira que eu prefiro, pois não há nenhum ponto em aumentar a complexidade desanexando os encadeamentos, pois os resources seriam liberados ao mesmo tempo, onde os poucos bytes de std::thread object de std::thread vs. maior complexidade e possivelmente mais primitivos de synchronization devem ser aceitáveis.

O destino do encadeamento após o encerramento do programa é um comportamento indefinido. mas um sistema operacional moderno irá limpar todos os threads criados pelo processo ao fechá-lo.

Ao desappend um std::thread , essas três condições continuarão a conter

  1. *this não possui mais nenhum segmento
  2. joinable () sempre será igual a false
  3. get_id () será igual a std :: thread :: id ()

Considere o seguinte código:

 #include  #include  #include  #include  void thread_fn() { std::this_thread::sleep_for (std::chrono::seconds(1)); std::cout < < "Inside thread function\n"; } int main() { std::thread t1(thread_fn); t1.detach(); return 0; } 

Executando-o em um sistema Linux, a mensagem do thread_fn nunca é impressa. O SO realmente limpa thread_fn() assim que main() sai. Substituir t1.detach() por t1.join() sempre imprime a mensagem conforme o esperado.

Quando o thread principal (ou seja, o thread que executa a function main ()) termina, o processo é finalizado e todos os outros threads param.

Referência: https://stackoverflow.com/a/4667273/2194843