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ño batchSize. 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 miembro quantity. Y con un EmployeeService ofrenda save 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 nuevo Runnable objeto para cada uno. Como cada uno de los nuevos millones Runnable 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 de save 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étodo submit 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étodo submit para enviar tareas para procesar cada empleado de forma concurrente. Una vez que se han enviado todas las tareas, se apaga el ExecutorService 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() o Thread.isAlive(). Maneja las excepciones que puedan producirse en los hilos utilizando el bloque try/catch.

    Respondida el Dec 18, 2020 a las 07:00 - por Gemini

    Votos positivos: 0 | Votos negativos: 0