Ele está vagamente relacionado a esta questão: std :: thread é agrupado em C ++ 11? . Embora a pergunta seja diferente, a intenção é a mesma:
Pergunta 1: ainda faz sentido usar seus próprios pools de thread (ou biblioteca de terceiros) para evitar a criação de thread cara?
A conclusão na outra pergunta foi que você não pode confiar std::thread
para ser agrupado (pode ou não ser). No entanto, std::async(launch::async)
parece ter uma chance muito maior de ser agrupado.
Não acho que seja forçado pelo padrão, mas IMHO eu esperaria que todas as boas implementações do C ++ 11 usassem o pool de threads se a criação de threads fosse lenta. Apenas em plataformas em que é barato criar um novo thread, eu esperaria que eles sempre gerassem um novo thread.
Pergunta 2: Isso é exatamente o que eu penso, mas não tenho fatos para provar isso. Posso muito bem estar enganado. É um palpite?
Finalmente, forneci aqui alguns códigos de amostra que mostram primeiro como acho que a criação de threads pode ser expressa por async(launch::async)
:
Exemplo 1:
thread t([]{ f(); });
// ...
t.join();
torna-se
auto future = async(launch::async, []{ f(); });
// ...
future.wait();
Exemplo 2: disparar e esquecer o tópico
thread([]{ f(); }).detach();
torna-se
// a bit clumsy...
auto dummy = async(launch::async, []{ f(); });
// ... but I hope soon it can be simplified to
async(launch::async, []{ f(); });
Pergunta 3: Você prefere as async
versões às thread
versões?
O resto já não faz parte da questão, mas apenas para esclarecimentos:
Por que o valor de retorno deve ser atribuído a uma variável fictícia?
Infelizmente, o padrão C ++ 11 atual força que você capture o valor de retorno std::async
, caso contrário, o destruidor é executado, o que bloqueia até que a ação termine. É considerado por alguns um erro no padrão (por exemplo, por Herb Sutter).
Este exemplo de cppreference.com ilustra bem:
{
std::async(std::launch::async, []{ f(); });
std::async(std::launch::async, []{ g(); }); // does not run until f() completes
}
Outro esclarecimento:
Eu sei que os pools de thread podem ter outros usos legítimos, mas nesta questão estou interessado apenas no aspecto de evitar custos de criação de thread caros .
Acho que ainda existem situações em que os pools de threads são muito úteis, especialmente se você precisar de mais controle sobre os recursos. Por exemplo, um servidor pode decidir lidar com apenas um número fixo de solicitações simultaneamente para garantir tempos de resposta rápidos e aumentar a previsibilidade do uso de memória. Pools de threads devem estar bem, aqui.
Variáveis de thread local também podem ser um argumento para seus próprios pools de thread, mas não tenho certeza se isso é relevante na prática:
- A criação de um novo encadeamento
std::thread
começa sem variáveis locais de encadeamento inicializadas. Talvez não seja isso que você deseja. - Em tópicos gerados por
async
, não está claro para mim porque o tópico poderia ter sido reutilizado. Do meu entendimento, as variáveis locais de thread não têm garantia de serem redefinidas, mas posso estar enganado. - Usar seus próprios pools de threads (de tamanho fixo), por outro lado, oferece controle total se você realmente precisar dele.
std::async()
. Ainda estou curioso para ver como eles oferecem suporte a destruidores thread_local não triviais em um pool de threads.
launch::async
, ela a trata como se fosse única launch::deferred
e nunca a executa de forma assíncrona - então, na verdade, essa versão de libstdc ++ "escolhe" sempre usar diferido, a menos que forçado de outra forma.
std::async
poderia ter sido uma coisa bonita para o desempenho - poderia ter sido o sistema padrão de execução de tarefas curtas, naturalmente apoiado por um pool de threads. No momento, é apenas um std::thread
com alguma porcaria acrescentada para tornar a função thread ser capaz de retornar um valor. Ah, e eles adicionaram funcionalidade "adiada" redundante que se sobrepõe std::function
completamente ao trabalho .
std::async(launch::async)
parece ter uma chance muito maior de ser agrupado." Não, eu acreditostd::async(launch::async | launch::deferred)
que pode ser agrupado. Com apenaslaunch::async
a tarefa deve ser iniciada em um novo thread, independentemente de quais outras tarefas estão em execução. Com a políticalaunch::async | launch::deferred
, a implementação pode escolher qual política, mas o mais importante, pode atrasar a escolha de qual política. Ou seja, ele pode esperar até que um thread em um pool de threads se torne disponível e, em seguida, escolher a política assíncrona.