c++ - example - Grupo de subprocesos para ejecutar tareas arbitrarias con diferentes prioridades
createthread c++ example (5)
@DrPizza: esta es una muy buena pregunta, y una que llega directamente al corazón del problema. Hay algunas razones por las que QueueUserWorkItem y el grupo de subprocesos de Windows NT se descartaron (aunque el Vista se ve interesante, tal vez en unos pocos años).
Sí, parece que se fortaleció bastante en Vista, ahora es bastante versátil.
De acuerdo, todavía no estoy del todo claro sobre cómo desea que funcionen las prioridades. Si el grupo ejecuta actualmente una tarea de tipo A con concurrencia máxima de 1 y baja prioridad, y se le asigna una nueva tarea también de tipo A (y concurrencia máxima 1), pero esta vez con una alta prioridad, ¿qué debería hacer? ?
La suspensión de la ejecución actual de A es peliagudo (podría contener un bloqueo que la nueva tarea necesita llevar a cabo, bloqueando el sistema). No puede engendrar un segundo hilo y simplemente dejarlo correr al costado (la concurrencia permitida es solo 1). Pero no puede esperar hasta que se complete la tarea de baja prioridad, porque el tiempo de ejecución no tiene límites y al hacerlo permitiría que una tarea de baja prioridad bloqueara una tarea de alta prioridad.
¿Mi presunción es que es el último comportamiento lo que buscas?
Estoy intentando crear un diseño para un grupo de subprocesos con muchos requisitos de diseño para mi trabajo. Este es un problema real para el software en funcionamiento, y es una tarea difícil. Tengo una implementación en funcionamiento, pero me gustaría compartir esto con SO y ver qué ideas interesantes pueden surgir, de modo que pueda comparar mi implementación y ver cómo se acumula. Intenté ser tan específico con los requisitos como puedo.
El grupo de subprocesos necesita ejecutar una serie de tareas. Las tareas pueden ser de corta duración (<1seg) o largas (horas o días). Cada tarea tiene una prioridad asociada (de 1 = muy bajo a 5 = muy alto). Las tareas pueden llegar en cualquier momento mientras se ejecutan las otras tareas, de modo que a medida que llegan, el grupo de subprocesos debe recogerlas y programarlas a medida que los hilos estén disponibles.
La prioridad de la tarea es completamente independiente de la duración de la tarea. De hecho, es imposible saber cuánto tiempo tardará en ejecutarse una tarea sin ejecutarla.
Algunas tareas están vinculadas a la CPU, mientras que otras están muy vinculadas a IO. Es imposible saber de antemano qué tarea sería (aunque creo que podría detectarse mientras se ejecutan las tareas).
El objetivo principal del grupo de subprocesos es maximizar el rendimiento. El grupo de subprocesos debería usar efectivamente los recursos de la computadora. Idealmente, para tareas vinculadas a la CPU, la cantidad de hilos activos sería igual a la cantidad de CPU. Para las tareas vinculadas de IO, se deben asignar más subprocesos que las CPU, de modo que el bloqueo no afecte excesivamente el rendimiento. Es importante minimizar el uso de cerraduras y el uso de contenedores seguros / rápidos para hilos.
En general, debe ejecutar tareas de mayor prioridad con una prioridad de CPU más alta (ref: SetThreadPriority). Las tareas de menor prioridad no deberían "bloquear" la ejecución de tareas de mayor prioridad, por lo que si se produce una tarea de mayor prioridad mientras se ejecutan todas las tareas de baja prioridad, se ejecutará la tarea de mayor prioridad.
Las tareas tienen un parámetro de "tareas en ejecución máxima" asociado a ellas. Cada tipo de tarea solo puede ejecutarse como máximo en esta cantidad de instancias simultáneas de la tarea a la vez. Por ejemplo, podríamos tener las siguientes tareas en la cola:
- A - 1000 instancias - baja prioridad - tareas máximas 1
- B - 1000 instancias - baja prioridad - tareas máximas 1
- C - 1000 instancias - baja prioridad - tareas máximas 1
Una implementación en funcionamiento solo podría ejecutarse (como máximo) 1 A, 1 B y 1 C al mismo tiempo.
Tiene que ejecutarse en Windows XP, Server 2003, Vista y Server 2008 (últimos service packs).
Como referencia, podríamos usar la siguiente interfaz:
namespace ThreadPool
{
class Task
{
public:
Task();
void run();
};
class ThreadPool
{
public:
ThreadPool();
~ThreadPool();
void run(Task *inst);
void stop();
};
}
Tiene que ejecutarse en Windows XP, Server 2003, Vista y Server 2008 (últimos service packs).
¿Qué característica de los grupos de subprocesos incorporados del sistema los hace inadecuados para su tarea? Si desea apuntar a XP y 2003, no puede usar los nuevos y brillantes pools de Vista / 2008, pero aún puede usar QueueUserWorkItem y sus amigos.
@DrPizza:
De acuerdo, todavía no estoy del todo claro sobre cómo desea que funcionen las prioridades. Si el grupo ejecuta actualmente una tarea de tipo A con concurrencia máxima de 1 y baja prioridad, y se le asigna una nueva tarea también de tipo A (y concurrencia máxima 1), pero esta vez con una alta prioridad, ¿qué debería hacer? ?
Este es un poco complicado, aunque en este caso creo que me gustaría simplemente permitir que la tarea de baja prioridad se ejecute hasta su finalización. Por lo general, no veríamos muchos tipos de tareas con prioridades de subprocesos diferentes. En nuestro modelo, es posible detener de forma segura y luego reiniciar las tareas en ciertos puntos bien definidos (por diferentes motivos), aunque las complicaciones que esto podría presentar probablemente no justifiquen el riesgo.
Normalmente, solo diferentes tipos de tareas tendrían diferentes prioridades. Por ejemplo:
- Una tarea - 1000 instancias - baja prioridad
- Tarea B - 1000 instancias - alta prioridad
Asumiendo que las tareas A habían llegado y se estaban ejecutando, entonces las tareas B habían llegado, desearíamos que las tareas B pudieran ejecutarse más o menos de inmediato.
@DrPizza: esta es una muy buena pregunta, y una que llega directamente al corazón del problema. Hay algunas razones por las que QueueUserWorkItem y el grupo de subprocesos de Windows NT se descartaron (aunque el Vista se ve interesante, tal vez en unos pocos años).
En primer lugar, queríamos tener un mayor control sobre cuándo se inicia y se detienen los hilos. Hemos escuchado que el grupo de subprocesos NT es reacio a iniciar un nuevo subproceso si cree que las tareas son de ejecución corta. Podríamos usar el WT_EXECUTELONGFUNCTION, pero realmente no tenemos idea si la tarea es larga o corta
En segundo lugar, si el grupo de subprocesos ya estaba lleno de tareas de baja prioridad y larga ejecución, no habría posibilidad de que una tarea de alta prioridad se ejecute de manera oportuna. El grupo de subprocesos NT no tiene un concepto real de prioridades de tareas, por lo que no podemos hacer un QueueUserWorkItem y decir "oh, por cierto, ejecute este inmediatamente".
En tercer lugar, (de acuerdo con MSDN) el grupo de subprocesos NT no es compatible con el modelo de apartamento STA. No estoy seguro de qué significará esto, pero todos nuestros hilos de trabajo se ejecutan en una STA.
Entonces, ¿qué vamos a elegir como el bloque de construcción básico para esto? Windows tiene dos bloques de construcción que parecen prometedores: - Puertos de finalización de E / S (IOCP) y Llamadas de procedimiento asincrónico (APC). Ambos nos dan colas FIFO sin tener que realizar un bloqueo explícito, y con una cierta cantidad de compatibilidad con el sistema operativo incorporado en lugares como el programador (por ejemplo, los IOCP pueden evitar algunos cambios de contexto).
Las APC son quizás un poco mejor, pero tendremos que tener un poco de cuidado con ellas, porque no son del todo "transparentes". Si el elemento de trabajo realiza una espera de alertable (:: SleepEx, :: WaitForXxxObjectEx, etc.) y accidentalmente enviamos un APC al subproceso, el APC recién enviado tomará el hilo, suspendiendo el APC que se ejecutó previamente hasta que el nuevo APC esté terminado. Esto es malo para nuestros requisitos de concurrencia y puede hacer que los desbordamientos de pila sean más probables.