c++ - run - visual studio ide c
Es la implementación de Visual C++ de std:: async usando un grupo de subprocesos legal (1)
La situación se ha aclarado un tanto en C ++ 17 por P0296R2 . A menos que la implementación de Visual C ++ documente que sus subprocesos no proporcionan garantías de progreso progresivo simultáneas (lo que generalmente no sería deseable), el grupo de subprocesos delimitados no se conforma (en C ++ 17).
La nota sobre "prioridades de subprocesos impuestas externamente" ha sido eliminada, tal vez porque ya es posible que el entorno evite el progreso de un programa en C ++ (si no por prioridad, luego suspendido, y si no es así, entonces por poder o falla de hardware).
Hay un "deber" normativo remanente en esa sección, pero pertenece (como lo mentioned ) solo a operaciones sin bloqueos, que pueden retrasarse indefinidamente mediante el acceso concurrente frecuente por otro hilo a la misma línea de caché (no simplemente el mismo atómico) variable). (Creo que en algunas implementaciones esto puede suceder incluso si los otros hilos solo están leyendo).
Visual C ++ utiliza el grupo de subprocesos de Windows ( CreateThreadpoolWork
de Vista si está disponible y QueueUserWorkItem
si no) al llamar a std::async
con std::launch::async
.
El número de subprocesos en el grupo es limitado. Si crea varias tareas que se ejecutan durante mucho tiempo sin dormir (incluida la realización de E / S), las próximas tareas de la cola no tendrán la oportunidad de funcionar.
El estándar (estoy usando N4140) dice que al usar std::async
con std::launch::async
... llama a
INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
(20.9.2, 30.3.1.2) como si fuera un Nuevo subproceso de ejecución representado por un objeto de subproceso con las llamadas aDECAY_COPY()
se evalúan en el subproceso que llamóasync
.
(§30.6.8p3, Énfasis mío)
El constructor std::thread
crea un nuevo hilo, etc.
Acerca de los hilos en general, dice (§1.10p3):
Las implementaciones deben garantizar que todos los subprocesos desbloqueados finalmente progresen. [ Nota: las funciones de biblioteca estándar pueden bloquear silenciosamente en E / S o bloqueos. Los factores en el entorno de ejecución, incluidas las prioridades de subprocesos impuestas desde el exterior, pueden evitar que una implementación garantice ciertas progresiones. - nota final ]
Si creo un conjunto de subprocesos de sistema operativo o std::thread
s, todos realizando algunas tareas muy largas (quizás infinitas), todos serán programados (al menos en Windows, sin interferir con prioridades, afinidades, etc.). Si programamos las mismas tareas para el grupo de subprocesos de Windows (o usamos std::async(std::launch::async, ...)
que hace eso), las tareas programadas posteriores no se ejecutarán hasta que las tareas anteriores finalicen .
¿Es esto legal, estrictamente hablando? ¿Y qué significa "eventualmente"?
El problema es que si las tareas programadas primero son de facto infinitas, el resto de las tareas no se ejecutarán. Entonces, los otros hilos (no los hilos del sistema operativo, sino "C ++ - hilos" de acuerdo con la regla de si-si) no progresarán.
Se puede argumentar que si el código tiene bucles infinitos, el comportamiento no está definido, y por lo tanto es legal.
Pero argumento que no necesitamos un bucle infinito del tipo problemático que el estándar dice hace que UB haga que eso suceda. El acceso a objetos volátiles, la realización de operaciones atómicas y las operaciones de sincronización son todos efectos secundarios que "desactivan" la suposición de que los bucles terminan.
(Tengo un montón de llamadas asincrónicas 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 malo sería lanzar demasiadas tareas que se ejecutan hasta que se libere un bloqueo / se levante una bandera y luego programar usando `std :: async (std :: launch :: async, ...) una tarea que levante la bandera . A menos que la palabra "eventualmente" signifique algo muy sorprendente, este programa debe terminar. ¡Pero bajo la implementación de VC ++ no lo hará!
Para mí, parece una violación del estándar. Lo que me hace pensar es la segunda oración en la nota. Los factores pueden evitar que las implementaciones hagan ciertas garantías de progreso hacia adelante. Entonces, ¿cómo se cumplen estas implementaciones?
Es como decir que puede haber factores que impidan que las implementaciones proporcionen cierto aspecto 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 varios hilos. Muy mal para ellos y sus factores. Si no pueden proporcionarles eso no es C ++.
¿Es esto una relajación del requisito? Si se interpreta así, se trata de una retirada completa del requisito, ya que no especifica cuáles son los factores y, lo que es más importante, qué garantías no pueden proporcionar las implementaciones.
Si no, ¿qué significa esa nota?
Recuerdo que las notas a pie de página no son normativas según 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 la comprensión o el uso del texto del documento. El documento debe ser utilizable sin las notas.
Énfasis mío Si considero el documento sin esa nota poco clara, me parece que los hilos deben progresar, std::async(std::launch::async, ...)
tiene el mismo efecto que si el funtor se ejecuta en un nuevo hilo , como si se creara usando std::thread
, y así los funtores despachados usando std::async(std::launch::async, ...)
deben progresar. Y en la implementación de VC ++ con el threadpool no lo hacen. Entonces VC ++ está en violación del estándar a este respecto.
Ejemplo completo, probado usando 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:
¿Hay una implementación de std :: async que utiliza el grupo de subprocesos? - Pero no cuestiona la legalidad, y no tiene una respuesta de todos modos.
std :: async - ¿Uso dependiente de la implementación? - Menciona que "los grupos de hilos no son realmente compatibles", pero se centra en
thread_local
variablesthread_local
(que pueden resolverse 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 progreso .