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 eachwhere Procesado='N'Proceso_y_Marco(Clave)endfor
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
¿Multitasking con GeneXus? Suerte en pila...
ResponderBorrarLo 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
hay varias formas de hacerlo, la mas "chancha" es hacer que Proceso_y_Marco sea compilado como proceso command line.
ResponderBorrarLuego 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.
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).
ResponderBorrarPara 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.
Este comentario ha sido eliminado por el autor.
ResponderBorrarMarcos y David:
ResponderBorrarGracias 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.
Y la solución fue?
BorrarEn 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().
BorrarSi 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.