Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running configurations with a limit

Tags:

java

I have a question on how to proceed with my code. My project is a tool which runs configurations one by one in the background. I would like to add a limit to it for the number of running configurations. For example, if I have 13 configurations I would like to run 5 configurations each time, so the order will be:

- Running 5 configurations
- All 5 configurations done running
- Running 5 configurations
- All 5 configurations done running
- Running 3 configurations
- All 3 configurations done running

The code for now, works as following:

public void runConfigurations(List<ConfigStruct> configurations) {
    for (ConfigStruct configuration : configurations) {
        try {
            configuration.run();
        } catch (ConfigurationException e) {
            continue;
        }
    }
}

For now, it runs each configuration one by one. The run method looks as following:

public void run() throws ConfigurationException {
    StringBuffer runCmd = generateGalishFullCommand(GalishFlags.RUN);
    try {
        ExternalCommandExecutor.execute(runCmd, "Failed to run " + name, true, true);
    }  catch (IOException e) {
        throw new ConfigurationException(e.getMessage());
    }
}

The signature of the execute looks as following:

public static String execute(final String cmd, final String error, final boolean runInBackground, final boolean retry) throws IOException;

At first, I though I can just run the last configuration every 5 configurations not in the background but its problematic. I can't execute not in the background the last configuration every 5 configurations because maybe the first configuration will finish last. How can I solve this issue?

EDIT:

when I print the configurations, it looks as follows:

[com.configStructs@3f15dbec, com.configStructs@31d2327e]

Also, the configurations is a list of configStructs.

like image 285
vesii Avatar asked May 15 '19 18:05

vesii


People also ask

What are run configurations?

Run configuration is a set of parameters, arguments, and settings that define how an application is launched.

What are configurations in PyCharm?

Each configuration is a named set of startup properties that define what to execute and what parameters and environment should be used. With different startup properties, you can define different ways that PyCharm uses to execute your script.


1 Answers

Sorry vesii if I don't fully understand or even misunderstand your problem, but your English is not very good and I have problems even after your comments to see what the problem is in using multiple threads.

Anyway, I suggest you make your ConfigStruct class implement the Runnable interface, which is easy because it already has a run() method. You only need to get rid of throwing a checked exception, so I further suggest to make ConfigurationException a RuntimeException which you don't have to declare in the method signature.

Unfortunately you have not provided a full MCVE, only code snippets. So I have to make up the rest in order to be able to compile and run your code. I just added a few simple helper/dummy classes. My solution looks like this:

package de.scrum_master.app;

public enum GalishFlags {
    RUN
}
package de.scrum_master.app;

public class ConfigurationException extends RuntimeException {
  private static final long serialVersionUID = 1L;

  public ConfigurationException(String message, Throwable cause) {
    super(message, cause);
  }
}
package de.scrum_master.app;

import java.io.IOException;

public class ExternalCommandExecutor {
  public static String execute(final String cmd, final String error, final boolean runInBackground, final boolean retry) throws IOException {
    System.out.println("Executing external command: " + cmd);
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return cmd;
  }
}

As you can see, the command executor waits for 100 ms after printing something onto the console. You can also change that to 1000 ms if you want the program to run slower or even randomise it in order to emulate commands taking different times to finish.

Now we need a little driver application in which we generate configurations and run them. The key for solving your problem of never running more than 5 threads at the same time is to create a fixed thread pool via Executors.newFixedThreadPool(5). The rest should be easy to understand.

package de.scrum_master.app;

import java.io.IOException;

public class ConfigStruct implements Runnable {
  private String name;

  public ConfigStruct(String name) {
    this.name = name;
  }

  @Override
  public void run() {
    StringBuffer runCmd = generateGalishFullCommand(GalishFlags.RUN);
    try {
      ExternalCommandExecutor.execute(runCmd.toString(), "Failed to run " + name, true, true);
    } catch (IOException e) {
      throw new ConfigurationException(e.getMessage(), e);
    }
  }

  private StringBuffer generateGalishFullCommand(GalishFlags run) {
    return new StringBuffer("Galish full command for ConfigStruct '" + name + "'");
  }
}
package de.scrum_master.app;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Application {
  public void runConfigurations(List<ConfigStruct> configurations) {
    for (ConfigStruct configuration : configurations) {
      try {
        configuration.run();
      } catch (ConfigurationException e) {
        continue;
      }
    }
  }

  public void runConfigurationsThreaded(List<ConfigStruct> configurations) {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    for (ConfigStruct configuration : configurations)
      executorService.execute(configuration);
    executorService.shutdown();
    try {
      executorService.awaitTermination(30, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    List<ConfigStruct> configurations = new ArrayList<>();
    for (int i = 1; i <= 13; i++)
      configurations.add(new ConfigStruct("Configuration " + i));

    long startTime = System.currentTimeMillis();
    new Application().runConfigurations(configurations);
    System.out.println("Total time (1 thread)  = " + (System.currentTimeMillis() - startTime) + " ms");
    System.out.println();

    startTime = System.currentTimeMillis();
    new Application().runConfigurationsThreaded(configurations);
    System.out.println("Total time (5 threads) = " + (System.currentTimeMillis() - startTime) + " ms");
  }
}

The console log will look something like this:

Executing external command: Galish full command for ConfigStruct 'Configuration 1'
Executing external command: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 8'
Executing external command: Galish full command for ConfigStruct 'Configuration 9'
Executing external command: Galish full command for ConfigStruct 'Configuration 10'
Executing external command: Galish full command for ConfigStruct 'Configuration 11'
Executing external command: Galish full command for ConfigStruct 'Configuration 12'
Executing external command: Galish full command for ConfigStruct 'Configuration 13'
Total time (1 thread)  = 1374 ms

Executing external command: Galish full command for ConfigStruct 'Configuration 1'
Executing external command: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 8'
Executing external command: Galish full command for ConfigStruct 'Configuration 10'
Executing external command: Galish full command for ConfigStruct 'Configuration 9'
Executing external command: Galish full command for ConfigStruct 'Configuration 11'
Executing external command: Galish full command for ConfigStruct 'Configuration 13'
Executing external command: Galish full command for ConfigStruct 'Configuration 12'
Total time (5 threads) = 344 ms

Please note:

  • When running in a single-threaded loop, the runtime is > 1,300 ms (13 x 100 ms).
  • When running in a with the thread pool of 5 threads, the runtime is > 300 ms (3 x 100 ms) - exactly what you would expect according to your requirement to process 5 configurations at the same time.
  • Because of multi-threading the log output is not straight 1 to 13 but a little bit different, here 8, 10, 9, 11, 13, 12 at the end. For different processing times per thread it would look even more different.

Update: If you want to see some more variation, just add a random element in the threads' sleep times and extend the logging a bit:

package de.scrum_master.app;

import java.io.IOException;
import java.util.Random;

public class ExternalCommandExecutor {
  private static final Random RANDOM = new Random();

  public static String execute(final String cmd, final String error, final boolean runInBackground, final boolean retry) throws IOException {
    long sleepTime = 100 + 100 * (RANDOM.nextInt(3));
    System.out.println("Executing external command: " + cmd + ", sleeping for " + sleepTime + " ms");
    try {
      Thread.sleep(sleepTime);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Finished execution: " + cmd);
    return cmd;
  }
}

Then the console log could look like this:

Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 1'
Executing external command: Galish full command for ConfigStruct 'Configuration 2', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 3', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 4', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 5', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 6', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 7', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 8', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 8'
Executing external command: Galish full command for ConfigStruct 'Configuration 9', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 9'
Executing external command: Galish full command for ConfigStruct 'Configuration 10', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 10'
Executing external command: Galish full command for ConfigStruct 'Configuration 11', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 11'
Executing external command: Galish full command for ConfigStruct 'Configuration 12', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 12'
Executing external command: Galish full command for ConfigStruct 'Configuration 13', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 13'
Total time (1 thread)  = 2314 ms

Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 2', sleeping for 300 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 3', sleeping for 200 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 5', sleeping for 300 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 4', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 6', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 7', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 1'
Finished execution: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 8', sleeping for 200 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 9', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 10', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 11', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 9'
Finished execution: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 12', sleeping for 200 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 13', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 8'
Finished execution: Galish full command for ConfigStruct 'Configuration 10'
Finished execution: Galish full command for ConfigStruct 'Configuration 11'
Finished execution: Galish full command for ConfigStruct 'Configuration 13'
Finished execution: Galish full command for ConfigStruct 'Configuration 12'
Total time (5 threads) = 609 ms

See how in single-threaded mode still everything is FIFO (first in, first out)?

Please also note that if you count the number of active (unfinished) threads on the console, it is never more than 5, no matter the execution time. At the end the last 5 threads finish. And still the total execution time is clearly smaller than in the single-threaded case.


Update 2: Last but not least, if you increase the number of elements in the main loop from 13 to a bigger number, say 100, you will notice that in the end the total execution time of the multi-threaded solution is roughly 1/5 (or generally 1 divided by the number of threads in the fixed thread pool) of the single-threaded solution. This is because the threads don't do much else but wait and print to the console. If they actually do more such as heavy calculations or a lot of I/O then the improvement will be less dramatic but still significant.

My try with 100 configuration elements yielded the following output (abbreviated):

Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 1'
(...)
Executing external command: Galish full command for ConfigStruct 'Configuration 100', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 100'
Total time (1 thread)  = 20355 ms

Executing external command: Galish full command for ConfigStruct 'Configuration 2', sleeping for 100 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
(...)
Executing external command: Galish full command for ConfigStruct 'Configuration 100', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 99'
Finished execution: Galish full command for ConfigStruct 'Configuration 93'
Finished execution: Galish full command for ConfigStruct 'Configuration 94'
Finished execution: Galish full command for ConfigStruct 'Configuration 95'
Finished execution: Galish full command for ConfigStruct 'Configuration 100'
Total time (5 threads) = 3923 ms

See? ~20 seconds / 5 = ~4 seconds

like image 89
kriegaex Avatar answered Oct 03 '22 05:10

kriegaex