A implementação do std :: async do Visual C ++ usando um pool de threads é legal

O Visual C ++ usa o pool de threads do Windows (do VistaCreateThreadpoolWork se disponível eQueueUserWorkItem se não) ao ligarstd::async comstd::launch::async.

O número de threads no pool é limitado. Se você criar várias tarefas que são executadas por um longo período sem dormir (incluindo a execução de E / S), as próximas tarefas na fila não terão chance de funcionar.

O padrão (estou usando o N4140) diz que o usostd::async comstd::launch::async

... chamadasINVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)&nbsp;(20.9.2, 30.3.1.2)como se estivesse em um novo encadeamento de execução representado por um objeto de encadeamento&nbsp;com as chamadas paraDECAY_COPY()&nbsp;sendo avaliado no segmento que chamouasync.

(§30.6.8p3, ênfase minha.)

std::threadO construtor de cria um novo thread, etc.

Sobre tópicos em geral, diz (§1.10p3):

As implementações devem garantir que todos os threads desbloqueados acabem progredindo. [Nota:&nbsp;As funções padrão da biblioteca podem bloquear silenciosamente E / S ou bloqueios. Fatores no ambiente de execução, incluindo prioridades de encadeamento impostas externamente, podem impedir que uma implementação faça certas garantias de progresso futuro. -nota final]

Se eu criar vários threads de SO oustd::threads, todos executando algumas tarefas muito longas (talvez infinitas), todos serão agendados (pelo menos no Windows; sem mexer em prioridades, afinidades etc.). Se agendarmos as mesmas tarefas para o pool de threads do Windows (ou usaremosstd::async(std::launch::async, ...)&nbsp;que faz isso), as tarefas agendadas posteriores não serão executadas até que as tarefas anteriores sejam concluídas.

Isso é legal, estritamente falando? E o que "eventualmente" significa?

O problema é que, se as tarefas agendadas primeiro foremde fato&nbsp;infinito, o restante das tarefas não será executado. Portanto, os outros threads (não os threads do SO, mas "C ++ - threads" de acordo com a regra como se) não farão progresso.

Pode-se argumentar que, se o código possui loops infinitos, o comportamento é indefinido e, portanto, é legal.

Mas eu argumento que não precisamos de um loop infinito do tipo problemático que o padrão diz que faz com que o UB faça isso acontecer. Acessar objetos voláteis, executar operações atômicas e operações de sincronização são todos efeitos colaterais que "desabilitam" a suposição sobre o encerramento de loops.

(Eu tenho várias chamadas assíncronas executando a seguinte lambda

auto lambda = [&] {
    while (m.try_lock() == false) {
        for (size_t i = 0; i < (2 << 24); i++) {
            vi++;
        }
        vi = 0;
    }
};

e o bloqueio é liberado somente após a entrada do usuário. Mas existem outros tipos válidos de loops infinitos legítimos.)

Se eu agendar algumas dessas tarefas, as tarefas agendadas depois delas não serão executadas.

Um exemplo muito malicioso seria lançar muitas tarefas que são executadas até que um bloqueio seja liberado / um sinalizador ser disparado e, em seguida, agendar usando `std :: async (std :: launch :: async, ...) uma tarefa que gera o sinalizador . A menos que a palavra "eventualmente" signifique algo muito surpreendente, este programa deve terminar. Mas sob a implementação do VC ++, não!

Para mim, isso parece uma violação do padrão. O que me faz pensar é a segunda frase da nota. Os fatores podem impedir que as implementações façam certas garantias de progresso futuro. Então, como essas implementações estão em conformidade?

É como dizer que pode haver fatores que impedem as implementações de fornecer certo aspecto de ordenação de memória, atomicidade ou mesmo a existência de vários encadeamentos de execução. As implementações hospedadas excelentes, mas em conformidade, devem suportar vários encadeamentos. Muito ruim para eles e seus fatores. Se eles não podem fornecê-los, isso não é C ++.

Isso é um relaxamento do requisito? Se interpretar isso, é uma retirada completa do requisito, pois ele não especifica quais são os fatores e, mais importante, quais garantias podem não ser fornecidas pelas implementações.

Se não - o que essa nota significa?

Lembro-me de que as notas de rodapé não são normativas de acordo com as Diretivas ISO / IEC, mas não tenho certeza sobre as notas. Encontrei nas diretivas ISO / IEC o seguinte:

24 notas

24.1 Finalidade ou justificativa

As notas são usadas para fornecer informações adicionais destinadas a ajudar no entendimento ou uso do texto do documento.O documento deve ser utilizado sem as notas.

Ênfase minha. Se eu considerar o documento sem essa nota pouco clara, parece-me que os tópicos precisam fazer progresso,std::async(std::launch::async, ...)&nbsp;tem o efeitoAté parece&nbsp;o functor é executado em um novo encadeamento, como se estivesse sendo criado usandostd::threade, portanto, um functores despachados usandostd::async(std::launch::async, ...)&nbsp;deve fazer progresso. E na implementação do VC ++ com o conjunto de threads eles não. Portanto, o VC ++ viola o padrão a esse respeito.

Exemplo completo, testado usando o VS 2015U3 no Windows 10 Enterprise 1607 no i5-6440HQ:

#include <iostream>
#include <future>
#include <atomic>

int main() {
    volatile int vi{};
    std::mutex m{};
    m.lock();

    auto lambda = [&] {
        while (m.try_lock() == false) {
            for (size_t i = 0; i < (2 << 10); i++) {
                vi++;
            }
            vi = 0;
        }
        m.unlock();
    };

    std::vector<decltype(std::async(std::launch::async, lambda))> v;

    int threadCount{};
    std::cin >> threadCount;
    for (int i = 0; i < threadCount; i++) {
        v.emplace_back(std::move(std::async(std::launch::async, lambda)));
    }

    auto release = std::async(std::launch::async, [&] {
        __asm int 3;
        std::cout << "foo" << std::endl;
        vi = 123;
        m.unlock();
    });

    return 0;
}

Com 4 ou menos, termina. Com mais de 4, não.

Perguntas semelhantes:

Existe uma implementação do std :: async que usa o pool de threads?&nbsp;- Mas não questiona a legalidade e não tem resposta de qualquer maneira.

std :: async - Uso dependente da implementação?&nbsp;- Menciona que "conjuntos de encadeamentos não são realmente suportados", mas se concentra emthread_local&nbsp;variáveis (que são solucionáveis mesmo que "não sejam diretas" ou não-triviais, como dizem a resposta e o comentário) e não abordam a nota próxima ao requisito de progredir.