PiensoPienso: Paralelizando programas.

En una KB GeneXus tengo una tabla de la forma

Tabla
*Clave       N(10)
Procesado    C(1)
Otros        VarChar(1000)

y un programa GeneXus que hace lo siguiente:

for each
   where Procesado='N'
   Proceso_y_Marco(Clave)
endfor

images (1)El Proceso_y_Marco() es un procedimiento que demora en promedio 1 segundo, aunque algunos demoran 5 minutos y otros menos de medio segundo. Este proceso marca el registro como procesado y puedo ejecutar varias programas de estos en paralelo con diferente parametro, pues no produce bloqueos entre ellos.

El proceso es bastante pesado y no pueden ejecutarse mas de 8 procesos simultáneos sin perjudicar la performance del resto del servidor donde ejecuta.

Hacer un programa que ejecute el proceso_y_marco() en paralelo, no superando los 8 procesos simultáneos. Explique los cambios a realizar en el código.

UPDATE: Segunda parte en Respuesta de Paralelizando Programas

Comentarios

  1. ¿Multitasking con GeneXus? Suerte en pila...

    Lo que podés hacer es tener 8 instancias del programa corriendo, pero cada programa ejecuta un solo hilo.

    Para eso vas a precisar poner una bandera en la tabla, EnProceso, C(1), default = N, y antes de empezar a procesar un registro lo cambias a S. No te olvides de hacer el commit para no bloquear a los demás procesos.

    for each
    where Procesado = 'N'
    where EnProceso = 'N'

    &seguir = TomoProceso(Clave)
    if &seguir = True
    Proceso_y_Marco(Clave)
    endif
    endfor

    donde TomoProceso está definido como
    parm(in:Clave, out:&seguir);

    &seguir = False
    for each
    where EnProceso = 'N'

    EnProceso = 'S'
    &seguir = True
    endfor
    commit

    ResponderBorrar
  2. hay varias formas de hacerlo, la mas "chancha" es hacer que Proceso_y_Marco sea compilado como proceso command line.
    Luego implementas un cmd que lo que haga es ejecutar ese proceso de tal forma que no quede esperando.

    El proceso Proceso_y_Marco al finalizar termina marcando, por lo que da info de cuando termina.

    Cambio el proceso general, me creo una lista con todos los registros a procesar.
    Para este ejemplo tomo un vector porque es mas simple ya que para el ejemplo se usa solo la clave.

    &totAProc = 0

    for each
    where Procesado='N'
    &totAProc += 1
    &vAProc(&totAProc) = Clave
    endfor

    // Ahora creo un Loop sin fin
    &indLst = 1 // Por cual comienzo a procesar
    Do While 1=1
    &CntToProc = 0
    If &indLst = 1 // Si es la primer corrida
    // Directamente proceso 8 de un golpe
    If &totAProc < 9 // Por si la cantidad total es menor o igual a 8
    &CntToProc = &totAProc
    Else
    &CntToProc = 8
    Endif
    Else
    // De lo contrario calculo cuantos procesé ya y en base al índice se si hay huecos
    &CntProc = 0
    For Each
    Where Procesado='S'
    &CntProc += 1
    EndFor
    If &CntProc = &totAProc // Me fijo si ya procesé todos salgo del Loop
    Exit
    Endif
    &DiffToProc = &indLst - &CntProc
    If &DiffToProc < 8 // Significa que alguno de los 8 terminó
    &CntToProc = 8 - &DiffToProc
    Endif
    Endif
    If &CntToProc > 0
    Do 'Procesar primeros X'
    Endif
    Sleep 5000 // Hago una espera para no estar golepando la base a cada rato

    EndDo

    Sub 'Procesar Primeros X'
    &i = 1
    Do while &i <= &CntToProc
    &Clave = &vAProc(&indLst)
    // Ejecuto el comando Shell que pasa parametro y ejecuta desconectado el proceso
    &exec = &programcmd + " " + Trim(&Clave)
    Shell(&exec)
    &indLst += 1 // Posiciono siguiente elemento de lista
    &i += 1
    Enddo

    EndSub

    Con esto te aseguras que siempre estarán corriendo 8 procesos en simultaneo (y no con multihilo, sino procesos reales)

    PD: Perdón por los espacios, el sistema de comentarios quita la identación.

    ResponderBorrar
  3. me faltó mencionar que la llamada al shell puede cambiarse por un submit de GeneXus, el submit es mucho más GeneXus Friendly, pero por la forma en que está implementado no aconsejo utilizarlo (pero esto ya es algo a más bajo nivel).
    Para el ejemplo y el tipo de aplicación seguramente cuadre mejor el submit (que pasaría el procesamiento a ser multihilo), funcionaría sin problemas sin que nadie tenga que hacer nada por fuera.

    ResponderBorrar
  4. Este comentario ha sido eliminado por el autor.

    ResponderBorrar
  5. Marcos y David:
    Gracias por las soluciones y los comentarios. Creo que ambas soluciones necesitan algunas cambios pero sirven ambas.

    Espero que les sirva a otros tambien.

    Hago otro post con mi solucion.

    ResponderBorrar
    Respuestas
    1. Y la solución fue?

      Borrar
    2. En su momento (fue en el 2010!) hicimos un programa que revisaba cuantas procesos habia corriendo en windows con un nombre especifico y si habia menos de 5 ejecutando hacia un submit de un procedure Proceso_Y_Marco().
      Si habia 5 o mas procoesos con ese nombre, esperaba unos segundos y volvia a intentarlo.
      Con eso, logramos bajar los tiempos de procesamiento muchisimo. Si teníamos que procesar muchas instancias (por ejemplo 1000) y lanzabamos muchos proceso en simultaneo, el sistema se ponia lento por la base de datos, por lo que limitamos la cantidad a 5 en simultaneo. Es una solucion no demasiado complicada que ayuda a bajar el tiempo de procesamiento.

      Borrar

Publicar un comentario

1) Lee el post
2) Poné tu opinión sobre el mismo.
Todos los comentarios serán leidos y la mayoría son publicados.

Entradas más populares de este blog

Aplicación monolítica o distribuida?

La nefasta influencia del golero de Cacho Bochinche en el fútbol uruguayo

Funcionalidades de GeneXus que vale la pena conocer: DATE Constants.