32 votos

Consejos para convertir una gran monolítico único subproceso de la aplicación de una arquitectura multiproceso?

Mi empresa principal producto es un gran monolítica de la aplicación de C++, utilizado por científicos de procesamiento de datos y visualización. Su código fuente se remonta tal vez de 12 o 13 años, y aunque hemos puesto de trabajo en la mejora y mantenimiento (uso de STL y Boost - cuando me uní a la mayoría de los contenedores eran costumbre, por ejemplo - totalmente actualizado para Unicode y el 2010 VCL, etc) hay un resto, problema muy importante: es totalmente único subproceso. Dado que es un procesamiento de datos y la visualización del programa, esto se está convirtiendo más y más de una discapacidad.

Soy tanto un desarrollador y el gerente de proyecto para la próxima versión, donde queremos hacer frente a este, y este va a ser un trabajo difícil en ambas áreas. Estoy buscando concretas, prácticas y asesoría arquitectónica en forma de abordar el problema.

El programa de flujo de datos podría ser algo como esto:

  • una ventana necesita para extraer los datos
  • En el método paint, va a llamar a un método GetData, a menudo cientos de veces por cientos de bits de datos en una pintura
  • Esto va a ir y calcular o leer desde un archivo o cualquier otra cosa que se requiere (a menudo muy complejo flujo de datos - piense en esto como los datos que fluyen a través de un complejo gráfico, cada nodo de la que realiza las operaciones)

Es decir, el mensaje de pintura de controlador de bloque, mientras que el procesamiento se realiza, y si los datos no ha sido calculado y almacenado en caché, esto puede ser un largo tiempo. A veces esto es cuestión de minutos. Caminos similares ocurren en otras partes del programa que realizar largas operaciones de procesamiento - el programa no responde por el todo el tiempo, a veces por horas.

Estoy buscando consejos sobre cómo abordar la modificación de este. Ideas prácticas. Tal vez cosas como:

  • patrones de diseño para solicitar los datos de forma asíncrona?
  • almacenar grandes colecciones de objetos que los hilos se pueden leer y escribir de forma segura?
  • el manejo de la invalidación de los conjuntos de datos, mientras que algo está tratando de leerlo?
  • hay patrones y técnicas para este tipo de problema?
  • lo que debería hacer que no he pensado?

No he hecho ninguna programación multiproceso desde mi Uni días hace un par de años, y creo que el resto de mi equipo está en una posición similar. Lo que yo sabía era académico, no es práctico, y no es en absoluto suficiente para que tengan la confianza de acercarse a este.

El objetivo último es disponer de un completo programa sensible, donde todos los cálculos y la generación de datos se hace en otros hilos y la interfaz de usuario es siempre sensible. De que no podría llegar en un solo ciclo de desarrollo :)


Edit: pensé que debía añadir un par más detalles acerca de la aplicación:

  • Es un 32-bit aplicación de escritorio para Windows. Cada copia de la licencia. Pretendemos mantener un escritorio, se ejecutan de forma local app
  • Utilizamos Embarcadero (anteriormente Borland C++ Builder 2010 para el desarrollo. Esto afecta a las bibliotecas paralelas que podemos usar, ya que la mayoría de parecer (?) para ser escrito para el GCC o MSVC sólo. Por suerte están trabajando activamente en el desarrollo y sus estándares de C++ el apoyo es mucho mejor de lo que solía ser. El compilador soporta estos Impulsar componentes.
  • Su arquitectura no es tan limpia como debería ser, y los componentes son a menudo demasiado estrechamente acoplados. Este es otro problema :)

Edición #2: Gracias por las respuestas hasta ahora!

  • Me sorprende que tantas personas han recomendado un multi-arquitectura de procesos (es la parte superior de votación de respuesta en el momento), no multithreading. Mi impresión es que es una muy Unix-ish estructura del programa, y no sé nada acerca de cómo se diseña o de obras. Hay recursos disponibles acerca de ello, en Windows? Es muy común que en Windows?
  • En términos de enfoques concretos para algunos de los múltiples hilos sugerencias, hay patrones de diseño para la solicitud asincrónica y el consumo de datos, o threadaware o asincrónica MVP de sistemas, o cómo diseñar una tarea orientada al sistema, o artículos y libros y post-liberación de las deconstrucciones que ilustran las cosas que funcionan y cosas que no funcionan? Podemos desarrollar toda esta arquitectura de nosotros mismos, por supuesto, pero es bueno para trabajar a partir de lo que otros han hecho antes y saber qué errores y trampas a evitar.
  • Un aspecto que no es mencionado en ninguna de las respuestas es el proyecto de la gestión de esta. Mi impresión es que la estimación de cuánto tiempo tomará y mantener un buen control del proyecto cuando se hace algo tan incierto como esto puede ser difícil. Esa es una razón por la que estoy después de recetas o prácticas de codificación, supongo, para guiar y limitar la codificación de la dirección tanto como sea posible.

Todavía no he marcado una respuesta para esta pregunta - esto no es debido a la calidad de las respuestas, lo cual es genial (y gracias), sino simplemente que debido al alcance de este yo estoy esperando más respuestas o discusión. Gracias a los que ya han respondido!

16voto

John Dibling Puntos 56814

Usted tiene un gran reto por delante. Yo tenía un problema similar por delante de mí -- 15 año de edad monolítico de subproceso único código base, no tomar ventaja de varios núcleos, etc. Hemos gastado una gran cantidad de esfuerzo en tratar de encontrar un diseño y solución viable y de trabajar.

Malas noticias primero. Será en algún lugar entre lo práctico y lo imposible para hacer que su único subproceso de la aplicación multiproceso. Un solo subproceso de la aplicación se basa en que el único subproceso-ness es la forma sutil y burdo. Un ejemplo es si el cálculo de la porción requiere de la intervención de la parte GUI. La interfaz gráfica de usuario se debe ejecutar en el hilo principal. Si intenta obtener estos datos directamente desde el motor del cómputo, es probable que se ejecutará en el bloqueo y las condiciones de carrera que se requieren rediseños importantes para revisión. Muchas de estas confianzas no surgir durante la fase de diseño, o incluso durante la fase de desarrollo, pero sólo después de una compilación de la versión se coloca en un medio hostil.

Más malas noticias. Programación de aplicaciones multiproceso es excepcionalmente difícil. Podría parecer bastante sencillo simplemente bloquear cosas y hacer lo que tienes que hacer, pero no lo es. Primero de todo, si usted bloquea todo a la vista que terminan serializar su aplicación, la negación de todas las ventajas de mutithreading en el primer lugar, mientras que sigue sumando en toda la complejidad. Incluso si usted consigue más allá de esto, la escritura de un defecto MP aplicación es bastante difícil, pero la escritura de un gran rendimiento en el MP de la aplicación es mucho más difícil. Usted puede aprender sobre el trabajo en una especie de bautismo por el fuego. Pero si usted está haciendo esto con el código de producción, especialmente legado código de la producción, de poner su negocio en riesgo.

Ahora, la buena noticia. Usted tiene opciones que no impliquen la refactorización toda tu app y le dará más de lo que usted busca. Una opción en particular, es fácil de implementar (en términos relativos), y mucho menos propenso a los defectos de hacer que su aplicación totalmente MP.

Usted puede crear instancias de varios ejemplares de la solicitud. Hacer que uno de ellos visible, y todos los otros invisibles. El uso visible de la aplicación, ya que la capa de presentación, pero no hacer el trabajo de cálculo allí. En su lugar, enviar mensajes (tal vez a través de sockets) a la invisible copias de su aplicación, que hacen el trabajo y enviar los resultados a la capa de presentación.

Esto podría parecer un hack. Y tal vez lo es. Pero va a obtener lo que usted necesita sin poner la estabilidad y el rendimiento de su sistema en tan gran riesgo. Además de que hay oculto beneficios. Una de ellas es que el invisible motor de copias de su aplicación tendrá acceso a su propio espacio de memoria virtual, lo que facilita el uso de todos los recursos del sistema. Esto también funciona muy bien. Si está ejecutando en un 2-core cuadro, usted podría spin off 2 copias de su motor. 32 núcleos? 32 copias. Usted consigue la idea.

15voto

Andrew McGregor Puntos 7641

Por lo tanto, hay una pista en la descripción del algoritmo en cuanto a cómo proceder:

a menudo bastante complejo flujo de datos - piense en esto como los datos que fluyen a través de un complejo gráfico, cada nodo de la que realiza las operaciones

Me gustaría mirar a hacer que el gráfico de flujo de datos a ser, literalmente, la estructura que hace el trabajo. Los enlaces en el gráfico se puede thread-safe colas, los algoritmos en cada nodo puede permanecer prácticamente sin cambios, excepto envuelto en un hilo que recoge los elementos de trabajo de una cola y los depósitos de los resultados en una. Usted podría ir un paso más allá y usar sockets y los procesos en lugar de las colas y los hilos; esto le permitirá difundir a través de múltiples máquinas si hay un beneficio en el rendimiento en hacer esto.

A continuación, la pintura y otra interfaz gráfica de usuario es necesario que los métodos se dividió en dos: una mitad a la cola de la obra, y la otra mitad para dibujar o utilizar los resultados como salen de la tubería.

Esto puede no ser posible si la aplicación presume que los datos globales. Pero si está bien contenida en las clases, como su descripción sugiere que puede ser, entonces esto podría ser la forma más sencilla de conseguir parallelised.

8voto

dthorpe Puntos 23314
  1. No intente multithread todo en el antiguo aplicación. El Multithreading, por el bien de decir que multiproceso es una pérdida de tiempo y dinero. Estás creando una aplicación que hace algo, no es un monumento a sí mismo.
  2. Perfil y estudio de la ejecución de los flujos de averiguar donde la aplicación pasa la mayor parte de su tiempo. Un profiler es una gran herramienta para ello, pero también lo es paso a paso a través del código en el depurador. Encontrar las cosas más interesantes en el paseo aleatorio.
  3. Separar la interfaz de usuario de ejecución de los cálculos. El uso de la cruz-subproceso de comunicaciones técnicas para enviar actualizaciones de la interfaz de usuario de la computación hilo.
  4. Como un efecto secundario de #3: pensar cuidadosamente acerca de reentrada: ahora que el equipo se ejecuta en segundo plano y el usuario puede pitufo todo en la interfaz de usuario, ¿qué cosas en la interfaz de usuario debe ser deshabilitado para evitar conflictos con la operación en segundo plano? Permite al usuario eliminar un conjunto de datos, mientras que un cálculo se ejecuta en los datos es probablemente una mala idea. (Mitigación: cálculo hace que un local de instantánea de los datos) ¿tiene sentido para el usuario a la cola de impresión de múltiples calcular operaciones simultáneamente? Si se manejan bien, esto podría ser una característica nueva y ayudar a racionalizar la aplicación de renovación esfuerzo. Si se ignora, será un desastre.
  5. Identificar las operaciones específicas, que son candidatos a los que se metieron en un subproceso en segundo plano. El candidato ideal es generalmente de una sola clase o función que hace un montón de trabajo (se requiere un "mucho tiempo" para completar más de un par de segundos) bien definidos, entradas y salidas, que hace uso de ninguna de recursos globales, y no toque la interfaz de usuario directamente. Evaluar y dar prioridad a los candidatos en función de cuánto trabajo se requiere para adaptar a este ideal.
  6. En términos de gestión de proyectos, tomar las cosas un paso a la vez. Si usted tiene múltiples operaciones que son fuertes candidatos para ser trasladado a un subproceso en segundo plano, y no tienen ninguna interacción de unos con otros, estos podrían ser implementadas en paralelo por varios desarrolladores. Sin embargo, sería un buen ejercicio para tener a todo el mundo a participar en una conversión primer lugar, para que todo el mundo entiende lo que debe buscar y establecer sus patrones de interfaz de usuario de interacción, etc. Mantenga un largo pizarra reunión para discutir el diseño y el proceso de extracción de una función en un subproceso en segundo plano. Ir a implementar que (juntos o repartir las piezas a las personas), a continuación, volverá a reunir para poner a todos juntos y discutir los descubrimientos y los puntos de dolor.
  7. Multithreading es un dolor de cabeza y requiere más cuidado en el pensamiento de recto hasta la codificación, pero la división de la aplicación en múltiples procesos crea muchos más dolores de cabeza, de la OMI. Roscado soporte y disponible primitivas son buenas en Windows, tal vez mejor que algunas otras plataformas. El uso de ellos.
  8. En general, no hagas más de lo necesario. Es fácil severamente sobre implementar y más de complicar un problema tirando más de los patrones y de las bibliotecas estándar.
  9. Si nadie en su equipo ha realizado múltiples subprocesos de trabajo antes, presupuesto de tiempo para hacer un experto o de los fondos para contratar a uno como un consultor.

7voto

John Knoeller Puntos 20754

La cosa principal que usted tiene que hacer es desconectar la interfaz de usuario de su conjunto de datos. Quisiera sugerir que la manera de hacerlo es poner una capa en el medio.

Usted tendrá que diseñar una estructura de datos de datos de cocido-de-pantalla. Esto es más probable que contienen copias de algunos de sus datos back-end, pero "cocinado" para ser fácil de extraer. La idea clave aquí es que esto es rápido y fácil a la pintura. Usted puede incluso tener esta estructura de datos contienen calcula las posiciones de la pantalla de bits de datos, por lo que es fácil de extraer.

Cada vez que reciba un mensaje WM_PAINT usted debe obtener la más reciente completa versión de esta estructura y sacar de ello. Si haces esto correctamente, usted debería ser capaz de manejar múltiples mensajes WM_PAINT por segundo debido a que el código de la pintura nunca se refiere a sus datos de back-end. Es simplemente girando a través de la cocidos estructura. La idea aquí es que es mejor pintar datos obsoletos rápidamente que para colgar su interfaz de usuario.

Mientras tanto...

Usted debe tener 2 copias completas de este cocida para la visualización de estructura. Uno es lo que el mensaje WM_PAINT mira. ( cfd_A) La otra es lo que la mano para su CookDataForDisplay() función. ( cfd_B). Su CookDataForDisplay() la función se ejecuta en un hilo separado, y trabaja en el desarrollo/actualización de cfd_B en el fondo. Esta función puede ser tan largo como se quiera porque no está interactuando con la pantalla en cualquier forma. Una vez que la llamada devuelve cfd_B será la más actualizada versión de la estructura.

Ahora swap cfd_A y cfd_B y función invalidaterect en la ventana de la aplicación.

Un modo simple de hacer esto es tener su cocido para la visualización de la estructura de ser un mapa de bits, y que podría ser un buen camino a seguir para conseguir el balanceo de la bola, pero estoy seguro que con un poco de pensamiento que usted puede hacer un trabajo mucho mejor con un más sofisticado de la estructura.

Así que, volviendo a tu ejemplo.

  • En el método paint, va a llamar a un método GetData, a menudo cientos de veces por cientos de bits de datos en una pintura

Este es ahora de 2 hilos, el método paint se refiere a cfd_A y se ejecuta en el subproceso de interfaz de usuario. Mientras tanto cfd_B está siendo construido por un subproceso en segundo plano el uso de GetData llamadas.

La rápida y desordenada manera de hacer esto es

  1. Tome su actual WM_PAINT código, pegarlo en una función denominada PaintIntoBitmap().
  2. Crear un mapa de bits y una Memoria de DC, este es cfd_B.
  3. Crear un hilo y pasar a cfd_B y tiene que llamar PaintIntoBitmap()
  4. Cuando este hilo completa, de intercambio cfd_B y cfd_A

Ahora su nuevo WM_PAINT método sólo se necesita la pre-renderizados de mapa de bits en cfd_A y lo dibuja en la pantalla. Su interfaz de usuario es ahora disconnnected de su backend GetData() función.

Ahora empieza el verdadero trabajo, debido a la rápida y desordenada manera no manija de la ventana de cambio de tamaño muy bien. Usted puede ir de allí para refinar lo que su cfd_A y cfd_B estructuras son un poco a la vez hasta llegar a un punto donde usted está satisfecho con el resultado.

6voto

Byron Whitlock Puntos 29863

Usted puede comenzar a romper el la interfaz de usuario y la tarea de trabajo en hilos separados.

En el método paint en lugar de llamar a getData() directamente, se pone a la solicitud en un hilo de seguridad de la cola. getData() se ejecuta en otro hilo que lee los datos de la cola. Cuando el getData hilo está hecho, es la señal de que el hilo principal para volver a dibujar la superficie de visualización con sus datos de resultado usando hilo de la sincronización para pasar los datos.

Mientras todo esto sucede, por supuesto, tienen una barra de progreso diciendo reticulantes estrías por lo que el usuario sabe que algo está pasando.

Esto mantendrá su interfaz de usuario ágil sin dolor significativo de multithreading sus rutinas de trabajo (que puede ser similar a una reescritura total)

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X