¿Es legal la implementación de Visual C ++ de std :: async usando un grupo de subprocesos?

Visual C ++ usa el grupo de subprocesos de Windows (Vista'sCreateThreadpoolWork si está disponible yQueueUserWorkItem si no) al llamarstd::async constd::launch::async.

El número de subprocesos en el grupo es limitado. Si crea varias tareas que se ejecutan durante mucho tiempo sin dormir (incluidas las E / S), las próximas tareas en la cola no tendrán la oportunidad de funcionar.

El estándar (estoy usando N4140) dice que usarstd::async constd::launch::async

... llamadasINVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)&nbsp;(20.9.2, 30.3.1.2)como en un nuevo hilo de ejecución representado por un objeto de hilo&nbsp;con las llamadas aDECAY_COPY()&nbsp;siendo evaluado en el hilo que llamóasync.

(§30.6.8p3, Énfasis mío).

std::threadEl constructor crea un nuevo hilo, etc.

Sobre los hilos en general dice (§1.10p3):

Las implementaciones deben garantizar que todos los hilos desbloqueados eventualmente progresen. [Nota:&nbsp;Las funciones de biblioteca estándar pueden bloquearse silenciosamente en E / S o bloqueos. Los factores en el entorno de ejecución, incluidas las prioridades de subprocesos impuestos externamente, pueden impedir que una implementación haga ciertas garantías de avance. -nota final]

Si creo un montón de hilos del sistema operativo ostd::threads, todos realizando algunas tareas muy largas (quizás infinitas), todas serán programadas (al menos en Windows; sin interferir con las prioridades, afinidades, etc.). Si programamos las mismas tareas para el grupo de subprocesos de Windows (o usamosstd::async(std::launch::async, ...)&nbsp;que hace eso), las tareas programadas posteriores no se ejecutarán hasta que finalicen las tareas anteriores.

¿Es esto legal, estrictamente hablando? ¿Y qué significa "eventualmente"?

El problema es que si las tareas programadas primero sonde facto&nbsp;infinito, el resto de las tareas no se ejecutarán. Por lo tanto, los otros subprocesos (no subprocesos del sistema operativo, pero "C ++ - subprocesos" de acuerdo con la regla as-if) no avanzarán.

Se puede argumentar que si el código tiene bucles infinitos, el comportamiento es indefinido y, por lo tanto, es legal.

Pero sostengo que no necesitamos un bucle infinito del tipo problemático que el estándar dice que hace que UB haga que eso suceda. Acceder a objetos volátiles, realizar operaciones atómicas y operaciones de sincronización son todos efectos secundarios que "deshabilitan" la suposición sobre la terminación de los bucles.

(Tengo un montón de llamadas asíncronas ejecutando la siguiente lambda

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

y el bloqueo se libera solo con la entrada del usuario. Pero hay otros tipos válidos de bucles infinitos legítimos).

Si programo un par de tales tareas, las tareas que programo después de ellas no se ejecutan.

Un ejemplo realmente malvado sería lanzar demasiadas tareas que se ejecutan hasta que se libera un bloqueo / se levanta una bandera y luego se programa usando `std :: async (std :: launch :: async, ...) una tarea que levanta la bandera . A menos que la palabra "eventualmente" signifique algo muy sorprendente, este programa debe finalizar. ¡Pero bajo la implementación de VC ++ no lo hará!

A mí me parece una violación de la norma. Lo que me hace preguntarme es la segunda oración de la nota. Los factores pueden impedir que las implementaciones hagan ciertas garantías de progreso hacia adelante. Entonces, ¿cómo se conforman estas implementaciones?

Es como decir que puede haber factores que impiden que las implementaciones proporcionen ciertos aspectos del orden de la memoria, la atomicidad o incluso la existencia de múltiples hilos de ejecución. Genial, pero las implementaciones alojadas conformes deben admitir múltiples hilos. Lástima para ellos y sus factores. Si no pueden proporcionarlos, eso no es C ++.

¿Es esto una relajación del requisito? Si se interpreta así, es un retiro completo del requisito, ya que no especifica cuáles son los factores y, lo que es más importante, qué garantías pueden no proporcionar las implementaciones.

Si no, ¿qué significa esa nota?

Recuerdo que las notas al pie no son normativas de acuerdo con las Directivas ISO / IEC, pero no estoy seguro de las notas. Encontré en las directivas ISO / IEC lo siguiente:

24 notas

24.1 Propósito o razón de ser

Las notas se utilizan para proporcionar información adicional destinada a ayudar a comprender o utilizar el texto del documento.El documento se podrá utilizar sin las notas.

El énfasis es mío. Si considero el documento sin esa nota poco clara, me parece que los hilos deben avanzar,std::async(std::launch::async, ...)&nbsp;tiene el efectocomo si&nbsp;el functor se ejecuta en un nuevo hilo, como si se hubiera creado usandostd::thready, por lo tanto, un functor enviado utilizandostd::async(std::launch::async, ...)&nbsp;Hay que progresar. Y en la implementación de VC ++ con el threadpool no lo hacen. Por lo tanto, VC ++ infringe el estándar a este respecto.

Ejemplo completo, probado con VS 2015U3 en Windows 10 Enterprise 1607 en 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;
}

Con 4 o menos termina. Con más de 4 no lo hace.

Preguntas similares:

¿Existe una implementación de std :: async que use el grupo de subprocesos?&nbsp;- Pero no cuestiona la legalidad, y de todos modos no tiene una respuesta.

std :: async - ¿Uso dependiente de la implementación?&nbsp;- Menciona que "los grupos de subprocesos no son realmente compatibles", pero se centra enthread_local&nbsp;variables (que es solucionable incluso si "no es sencillo" o no es trivial como dicen la respuesta y el comentario) y no aborda la nota cerca del requisito de avanzar.