Cómo los hilos de uso en groovy para iterar 0.4 millones de registros
// this query returns 0.45 million records and stored in the list.
List empList=result.getQuery(query);
Iterating employee list and setting property and finally calling service method to save employee object. usando el método de proceso secuencial su toma de mucho tiempo debido al volumen de registros así que quiero utilizar hilos. Soy nuevo en groovy y implemento sólo ejemplos simples.
¿Cómo utilizar los hilos para la lógica inferior usando groovy?
for (Employee employee : empList) {
employee.setQuantity(8);
employeeService.save(employee);
}
Pregunta hecha hace 3 años, 4 meses, 28 días - Por algorithmalchemist
4 Respuestas:
-
Hay marcos para hacer esto (gpars viene a la mente) y también el marco de los ejecutores de java es una abstracción mejor que los hilos rectos, pero si queremos mantener las cosas realmente primitivas, usted puede dividir su lista en lotes y ejecutar cada lote en un hilo separado utilizando algo como:
def employeeService = new EmployeeService() def empList = (1..400000).collect { new Employee() } def batchSize = 10000 def workerThreads = empList.collate(batchSize).withIndex().collect { List
batch, int index -> Thread.start("worker-thread-${index}") { println "worker ${index} starting" batch.each { Employee e -> e.quantity = 8 employeeService.save(e) } println "worker ${index} completed" } } println "main thread waiting for workers to finish" workerThreads*.join() println "workers finished, exiting..." class Employee { int quantity } class EmployeeService { def save(Employee e) { Thread.sleep(1) } } que, cuando se ejecuta, imprime:
─➤ groovy solution.groovy worker 7 starting worker 11 starting worker 5 starting worker 13 starting worker 17 starting worker 16 starting worker 2 starting worker 18 starting worker 6 starting worker 15 starting worker 12 starting worker 14 starting worker 1 starting worker 4 starting worker 10 starting worker 8 starting worker 9 starting worker 3 starting worker 0 starting worker 20 starting worker 21 starting worker 19 starting worker 22 starting worker 24 starting worker 23 starting worker 25 starting worker 26 starting worker 27 starting worker 28 starting worker 29 starting worker 30 starting worker 31 starting worker 32 starting worker 33 starting worker 34 starting worker 35 starting worker 36 starting worker 37 starting worker 38 starting worker 39 starting main thread waiting for workers to finish worker 0 completed worker 16 completed worker 20 completed worker 1 completed worker 3 completed worker 14 completed worker 7 completed worker 12 completed worker 24 completed worker 10 completed worker 6 completed worker 19 completed worker 33 completed worker 27 completed worker 28 completed worker 35 completed worker 17 completed worker 25 completed worker 38 completed worker 4 completed worker 8 completed worker 13 completed worker 9 completed worker 39 completed worker 15 completed worker 36 completed worker 37 completed worker 18 completed worker 30 completed worker 23 completed worker 11 completed worker 32 completed worker 2 completed worker 29 completed worker 26 completed worker 5 completed worker 22 completed worker 31 completed worker 21 completed worker 34 completed workers finished, exiting...
List.collate
divide la lista de empleados en pedazos (List
) de tamañobatchSize
.withIndex
está justo ahí para que cada lote también consiga un índice (es decir, sólo un número 0, 1, 2, 3...) para la depuración y localización.Como estamos empezando los hilos, necesitamos esperar a que se completen, los
workerThreads*.join()
es esencialmente hacer lo mismo que:workerThreds.each { t -> t.join() }
pero usando una sintaxis más concisa y
Thread.join()
es una construcción de java para esperar un hilo para completar.Respondida el Dec 17, 2020 a las 06:52 - por bytebard
Votos positivos: 0 | Votos negativos: 0 -
Utilice la base de datos, no Java
As comentado por cfrick, en el trabajo real usted estaría utilizando SQL para hacer una actualización masiva de filas. En cambio, el objeto de bucle por objeto en Java para actualizar fila por fila en la base de datos sería muy lento en comparación con un simple
UPDATE…
en SQL.Pero por el bien de la exploración, ignoraremos este hecho, y procederemos con su Pregunta.
Probar hilos virtuales con Project Loom
El correcto Respuesta de Matias Bjarland me inspiró a probar código similar usando el Project Loom tecnología llegando a Java. Proyecto Loom trae hilos virtuales (fibras) para mayor concurrencia con codificación más simple.
Project Loom todavía está en fase experimental, pero está buscando comentarios de la comunidad Java. Obras especiales de Java 16 con tecnología Project Loom incorporadas son disponible ahora para el sistema operativo Linux/Mac/Windows.
Mi código aquí usa sintaxis Java, ya que no conozco a Groovy.
Quiero probar código similar a la otra Respuesta, creando un simple
Employee
con un solo campo miembroquantity
. Y con unEmployeeService
ofrendasave
método que simula escribir a una base de datos simplemente durmiendo un segundo completo.Una característica importante de Project Loom es que bloquear un hilo, y cambiar para trabajar en otro hilo, ahora se convierte en muy barato. Tantos de los trucos y técnicas utilizados por escrito código Java para evitar costosos bloqueos se hicieron innecesarios. Así que el batido visto en la otra Respuesta no debe ser necesario al usar hilos virtuales. Así que el código de abajo simplemente bucea medio millón
Employee
objetos, y crea un nuevoRunnable
objeto para cada uno. Como cada uno de los nuevos millonesRunnable
objetos son instantáneas, se envían a un servicio de ejecución.Corremos este código dos veces, usando dos tipos de servicios de ejecución. Uno es el tipo convencional usando hilos de plataforma / canal usados durante muchos años en Java antes de Project Loom, específicamente, el servicio de ejecución respaldado por una piscina de hilo fijo. El otro tipo es el nuevo servicio de ejecución ofrecido en Project Loom para hilos virtuales.
Código
package work.basil.example; import java.time.Duration; import java.time.Instant; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; import java.util.stream.IntStream; public class HalfMillion { public static void main ( String[] args ) { HalfMillion app = new HalfMillion(); app.demo(); } private void demo ( ) { System.out.println( "java.runtime.version " + System.getProperty( "java.runtime.version" ) ); System.out.println( "INFO - `demo` method starting. " + Instant.now() ); // Populate data. List < Employee > employees = IntStream.rangeClosed( 1 , 500_000 ).mapToObj( i -> new Employee() ).collect( Collectors.toList() ); // Submit task (updating field in each object) to an executor service. long start = System.nanoTime(); EmployeeService employeeService = new EmployeeService(); try ( //ExecutorService executorService = Executors.newFixedThreadPool( 5 ) ; // 5 of 6 real cores, no hyper-threading. ExecutorService executorService = Executors.newVirtualThreadExecutor() ; ) { employees .stream() .forEach( employee -> { executorService.submit( new Runnable() { @Override public void run ( ) { employee.quantity = 8; employeeService.save( employee ); } } ); } ); } // With Project Loom, the code blocks here until all submitted tasks have finished. Duration duration = Duration.ofNanos( System.nanoTime() - start ); // Report. System.out.println( "INFO - Done running demo for " + employees.size() + " employees taking " + duration + " to finish at " + Instant.now() ); } class Employee { int quantity; @Override public String toString ( ) { return "Employee{ " + "quantity=" + quantity + " }"; } } class EmployeeService { public void save ( Employee employee ) { //System.out.println( "TRACE - An `EmployeeService` is doing `save` on an employee." ); try {Thread.sleep( Duration.ofSeconds( 1 ) );} catch ( InterruptedException e ) {e.printStackTrace();} } } }
Resultados
Corrí ese código en un Mac mini (2018) con 3 GHz procesador Intel Core i5 que tiene 6 núcleos reales y sin hiper-telección, con 32 GB 2667 memoria DDR4 MHz, y ejecutar macOS Mojave 10.14.6.
Usando los nuevos hilos virtuales del Proyecto Loom
Uso
Executors.newVirtualThreadExecutor()
tomas bajo 5 segundos.java.runtime.version 16-loom+9-316 INFO - `demo` method starting. 2020-12-21T09:20:36.273351Z INFO - Done running demo for 500000 employees taking PT4.517136095S to finish at 2020-12-21T09:20:40.885315Z
Si he habilitado el
println
línea dentro desave
método, tomó 15 segundos.Usando una piscina fija de 5 hilos convencionales de plataforma / canal
Uso
Executors.newFixedThreadPool( 5 )
toma ... bueno, *mucho más largo. Durante un día en lugar de segundos: 27 horas.java.runtime.version 16-loom+9-316 INFO - `demo` method starting. 2020-12-21T09:32:07.173561Z INFO - Done running demo for 500000 employees taking PT27H58M18.930703698S to finish at 2020-12-22T13:30:28.813345Z
Conclusión
Bueno, no estoy seguro de que pueda sacar una conclusión aquí.
Los resultados para la piscina de hilo convencional tienen sentido. Recuerde que cada hilo Java mapas a un hilo de núcleo en el sistema operativo host. Si estamos durmiendo un segundo por objeto empleado, ya que saturamos 5 núcleos habrá sobre todo 5 hilos durmiendo la mayor parte del tiempo. Esto significa que la duración total debe ser al menos cien mil segundos.
Los resultados de los hilos virtuales en Project Loom no son creíbles. El comando para dormir el hilo actual parece ignorar al usar hilos virtuales. Pero no estoy seguro; tal vez mis cinco núcleos físicos en este Mac fueron capaces de dormir simultáneamente cerca de cien mil hilos cada uno?
Por favor, publique críticas si encuentras falla con mi código o enfoque. No soy un experto en rosca y concurrencia.
Respondida el Dec 17, 2020 a las 06:58 - por binarybard
Votos positivos: 0 | Votos negativos: 0 -
Para iterar sobre 0.45 millones de registros de forma más eficiente utilizando hilos en Groovy, puedes utilizar la clase
ExecutorService
de Java y el métodosubmit
para ejecutar tareas con hilos. Aquí tienes un ejemplo de cómo podrías modificar tu código para hacer uso de hilos:groovy import java.util.concurrent.* // Crear un ExecutorService con un pool de hilos para procesamiento concurrente ExecutorService executor = Executors.newFixedThreadPool(10) // Iterar sobre la lista de empleados y crear tareas para procesar cada empleado en un hilo empList.each { employee -> executor.submit({ employee.setQuantity(8) employeeService.save(employee) } as Callable) } // Apagar el ExecutorService después de que hayan terminado todas las tareas executor.shutdown() executor.awaitTermination(1, TimeUnit.HOURS) // Esperar hasta que todas las tareas se completen o haya transcurrido 1 hora // Opcionalmente, puedes verificar si todas las tareas se completaron con éxito if (executor.isTerminated()) { println "Todos los empleados se han procesado con éxito" } else { println "Algunas tareas podrían no haberse completado correctamente" }
En este código, se crea un
ExecutorService
con un pool de 10 hilos, y luego se utiliza el métodosubmit
para enviar tareas para procesar cada empleado de forma concurrente. Una vez que se han enviado todas las tareas, se apaga elExecutorService
y se espera a que todas las tareas se completen o haya transcurrido un tiempo determinado.Ten en cuenta que es importante tener en cuenta la concurrencia al manipular y guardar los objetos
Employee
para evitar posibles problemas de concurrencia y asegurarte de que tu aplicación sea segura para hilos.Respondida el Dec 18, 2020 a las 06:47 - por Gpt
Votos positivos: 0 | Votos negativos: 0 -
Utilizar hilos en Groovy para iterar sobre una gran lista y realizar operaciones
1. Definir la clase de hilo:
groovy class EmployeeProcessorThread implements Runnable { private Employee employee EmployeeProcessorThread(Employee employee) { this.employee = employee } @Override void run() { // Establece la propiedad y llama al servicio para guardar el empleado employee.quantity = 8 employeeService.save(employee) } }
2. Crear una lista de hilos:
groovy List
threads = [] for (Employee employee : empList) { threads << new Thread(new EmployeeProcessorThread(employee)) } 3. Iniciar los hilos:
groovy threads.each { it.start() }
4. Esperar a que los hilos terminen:
groovy threads.each { it.join() }
5. Código completo:
groovy // Consulta y almacena los registros en la lista List
empList = result.getQuery(query) // Crea una lista de hilos List threads = [] for (Employee employee : empList) { threads << new Thread(new EmployeeProcessorThread(employee)) } // Inicia los hilos threads.each { it.start() } // Espera a que los hilos terminen threads.each { it.join() } Consejos:
Puedes ajustar el número de hilos según la capacidad de tu máquina y la cantidad de registros. Considera utilizar un grupo de hilos para administrar y programar los hilos. Monitorea el progreso de los hilos utilizando métodos como
Thread.getState()
oThread.isAlive()
. Maneja las excepciones que puedan producirse en los hilos utilizando el bloquetry/catch
.Respondida el Dec 18, 2020 a las 07:00 - por Gemini
Votos positivos: 0 | Votos negativos: 0