Implementar el "robo de trabajo" no es difícil en teoría. Necesita un conjunto de colas que contengan tareas que funcionen haciendo una combinación de computación y generando otras tareas para hacer más trabajo. Y necesita acceso atómico a las colas para colocar tareas recién generadas en esas colas.Finalmente, necesita un procedimiento que cada tarea llame al final, para encontrar más trabajo para el hilo que ejecutó la tarea; ese procedimiento necesita buscar en colas de trabajo para encontrar trabajo.
La mayoría de estos sistemas de robo de trabajo suponen que hay un pequeño número de subprocesos (respaldados típicamente por núcleos de procesador reales), y que hay exactamente una cola de trabajos por subproceso. Luego, primero intenta robar el trabajo de su propia cola, y si está vacío, intente robarle a los demás. Lo que se complica es saber qué colas buscar; escanearlos en serie para el trabajo es bastante caro y puede crear una gran cantidad de disputas entre los hilos que buscan trabajo.
Hasta ahora, todo esto es bastante genérico con dos excepciones importantes: 1) los contextos de conmutación (por ejemplo, establecer registros de contexto del procesador como una "pila") no se pueden expresar en C o C++ puros. Puede resolver esto aceptando escribir parte de su paquete en el código máquina específico de la plataforma objetivo. 2) El acceso atómico a las colas para un multiprocesador no se puede realizar puramente en C o C++ (ignorando el algoritmo de Dekker), por lo que deberá codificar las primitivas de sincronización de lenguaje ensamblador como X86 LOCK XCH o Compare and Swap. Ahora, el código involucrado en la actualización de la queuse una vez que tenga acceso seguro no es muy complejo, y usted podría escribirlo fácilmente en unas pocas líneas de C.
Sin embargo, creo que encontrará es que intentar codificar tales un paquete en C y C++ con ensamblador mixto todavía es bastante ineficiente y eventualmente terminará codificando todo en ensamblador de todos modos. Bien está que queda son puntos/C de entrada compatibles ++ C: -}
Hice esto para nuestra PARLANSE lenguaje de programación paralelo, lo que ofrece la idea de un número arbitrariamente grande de cálculos paralelos vivir e interactuar (Sincronizando) en cualquier instante. Se implementa entre bastidores en un X86 exactamente con un hilo por CPU, y la implementación está completamente en ensamblador. El código de robo de trabajo es probablemente de 1000 líneas en total, y su código complicado porque desea que sea extremadamente rápido en el caso de no contención.
El vuelo real en la pomada para C y C++ es, cuando se crea una tarea que representa el trabajo, ¿cuánto espacio de pila se asigna? Los programas serie C/C++ evitan esta pregunta simplemente sobreasignando grandes cantidades (por ejemplo, 10Mb) de una pila lineal, y a nadie le importa mucho la cantidad de ese espacio de pila que se desperdicia. Pero si puede crear miles de tareas y hacer que todas ellas vivan en un instante determinado, no puede asignar razonablemente 10Mb a cada una. Entonces, ahora es necesario determinar de forma estática cuánto espacio de pila necesitará una tarea (Turing-hard), o tendrá que asignar trozos de pila (por ejemplo, por llamada de función), que los compiladores C/C++ ampliamente disponibles no hacen (por ejemplo, el que probablemente esté usando). La última salida es estrangular la creación de tareas para limitarla a unos pocos cientos en cualquier instante, y multiplexar unos cientos de montones realmente enormes entre las tareas que están activas. No puede hacer lo último si las tareas pueden interbloquear/suspender el estado, porque se ejecutará en su umbral. Entonces solo puede hacer esto si las tareas solo hacen el cálculo. Eso parece una restricción bastante severa.
Para PARLANSE, creamos un compilador que asigna registros de activación en el montón para cada llamada de función.
TBB es mucho más masivo y complejo para mis necesidades. Estoy buscando una implementación mucho más simple, "dedicada" ... si hay –