Está vagamente relacionado con esta pregunta: ¿std :: thread está agrupado en C ++ 11? . Aunque la pregunta es diferente, la intención es la misma:
Pregunta 1: ¿Todavía tiene sentido usar su propio grupo de subprocesos (o una biblioteca de terceros) para evitar la creación de subprocesos costosa?
La conclusión en la otra pregunta fue que no se puede confiar en std::thread
que se agruparán (podría serlo o no). Sin embargo, std::async(launch::async)
parece tener una probabilidad mucho mayor de agruparse.
No creo que esté forzado por el estándar, pero en mi humilde opinión, esperaría que todas las buenas implementaciones de C ++ 11 usen la agrupación de subprocesos si la creación de subprocesos es lenta. Solo en plataformas donde es económico crear un nuevo hilo, esperaría que siempre generen un nuevo hilo.
Pregunta 2: Esto es solo lo que pienso, pero no tengo hechos que lo prueben. Bien puedo estar equivocado. ¿Es una suposición fundamentada?
Finalmente, aquí he proporcionado un código de muestra que primero muestra cómo creo que la creación de subprocesos se puede expresar mediante async(launch::async)
:
Ejemplo 1:
thread t([]{ f(); });
// ...
t.join();
se convierte en
auto future = async(launch::async, []{ f(); });
// ...
future.wait();
Ejemplo 2: disparar y olvidar hilo
thread([]{ f(); }).detach();
se convierte en
// a bit clumsy...
auto dummy = async(launch::async, []{ f(); });
// ... but I hope soon it can be simplified to
async(launch::async, []{ f(); });
Pregunta 3: ¿Preferiría las async
versiones a las thread
versiones?
El resto ya no es parte de la pregunta, sino solo para aclarar:
¿Por qué se debe asignar el valor de retorno a una variable ficticia?
Desafortunadamente, el estándar actual de C ++ 11 obliga a capturar el valor de retorno std::async
, ya que de lo contrario se ejecuta el destructor, que se bloquea hasta que finaliza la acción. Algunos lo consideran un error en el estándar (por ejemplo, por Herb Sutter).
Este ejemplo de cppreference.com lo ilustra muy bien:
{
std::async(std::launch::async, []{ f(); });
std::async(std::launch::async, []{ g(); }); // does not run until f() completes
}
Otra aclaración:
Sé que los grupos de subprocesos pueden tener otros usos legítimos, pero en esta pregunta solo me interesa el aspecto de evitar costosos costos de creación de subprocesos .
Creo que todavía hay situaciones en las que los grupos de subprocesos son muy útiles, especialmente si necesita más control sobre los recursos. Por ejemplo, un servidor puede decidir manejar solo un número fijo de solicitudes simultáneamente para garantizar tiempos de respuesta rápidos y aumentar la previsibilidad del uso de la memoria. Los grupos de subprocesos deberían estar bien, aquí.
Las variables locales de subprocesos también pueden ser un argumento para sus propios grupos de subprocesos, pero no estoy seguro de si es relevante en la práctica:
- Crear un nuevo hilo con
std::thread
inicios sin variables locales de hilo inicializadas. Quizás esto no es lo que quieres. - En los subprocesos generados por
async
, no está claro para mí porque el subproceso podría haberse reutilizado. Según tengo entendido, no se garantiza que se restablezcan las variables locales de subprocesos, pero puedo estar equivocado. - El uso de sus propios grupos de subprocesos (de tamaño fijo), por otro lado, le brinda un control total si realmente lo necesita.
std::async()
. Todavía tengo curiosidad por ver cómo admiten destructores thread_local no triviales en un grupo de subprocesos.
launch::async
entonces la trata como si fuera única launch::deferred
y nunca la ejecuta de forma asincrónica, por lo que, en efecto, esa versión de libstdc ++ "elige" utilizar siempre diferido a menos que se fuerce de otra manera.
std::async
podría haber sido algo hermoso para el rendimiento; podría haber sido el sistema estándar de ejecución de tareas de ejecución corta, naturalmente respaldado por un grupo de subprocesos. En este momento, es solo una std::thread
con algunas tonterías agregadas para que la función del hilo pueda devolver un valor. Ah, y agregaron una funcionalidad "diferida" redundante que se superpone std::function
completamente al trabajo .
std::async(launch::async)
parece tener una probabilidad mucho mayor de ser agrupada". No, creostd::async(launch::async | launch::deferred)
que se puede agrupar. Con sololaunch::async
la tarea se supone que debe iniciarse en un nuevo hilo independientemente de qué otras tareas se estén ejecutando. Con la políticalaunch::async | launch::deferred
, la implementación puede elegir qué política, pero lo más importante es retrasar la elección de qué política. Es decir, puede esperar hasta que un subproceso en un grupo de subprocesos esté disponible y luego elegir la política asincrónica.