【问题标题】:Separate a for loop into multithreaded - Java将 for 循环分离为多线程 - Java
【发布时间】:2021-06-29 21:54:04
【问题描述】:

我必须使用 Java 进行蒙特卡罗模拟。我的情况如下:

for (int w = 0; w < n; w++) {

   for (int t = 0; t < 25; t++) {

      for (int a = 0; a < 20; a++) {

           // ...calculations...

      }
   }
}

n 往往非常大(数百万)。此外,ta 循环(以及内部的计算)独立于w 循环(我使用w 循环重复矩阵计算n 次)。这意味着,我真的不在乎哪个 w 在之前或之后运行。

是否有一种(可能并不复杂,因为我从未使用过并行编程)分割外部 for 循环并使用不同线程同步执行的方法(例如,如果我有四核处理器来运行所有线程,而不仅仅是一)?

在@tevemadar 解决方案之后编辑。

据我了解,我可以这样做:


public class MyMonteCarloClass {
  private static double[][] monteCarloSums = new double[20][25];
  Random generator = new Random();
  
  private void incrementSum() {
    for (int t = 0; t < 25; t++) {
      for (int a =0; a < 20; a++) {
        monteCarloSums[a][t] += generator.nextGaussian();
      }
    }
  }
  
  public double[][] getValue(int numberOfSim) {
    IntStream.range(0, numberOfSim).parallel().forEach(I -> incrementSum());
    return this.monteCarloSums
  }
}

相对于三个嵌套循环,这样的事情会加快速度吗?

【问题讨论】:

标签: java multithreading parallel-processing


【解决方案1】:

使用IntStream,您可以轻松重写“经典”计数循环,

for(int i=0;i<10;i++) {
  System.out.print(i);
}

作为

IntStream.range(0, 10).forEach(i->{
  System.out.print(i);
});

他们都将打印0123456789
然后可以并行处理一个流:

IntStream.range(0, 10).parallel().forEach(i->{
  System.out.print(i);
});

它会突然产生一个混合顺序,比如6589724310。所以它是并行运行的,你不必处理线程、执行器、任务等。

你必须处理几件事情:

  • 就像匿名内部类中的方法一样,lambda 函数只能从外部范围访问“有效最终”变量。所以如果你在循环前面有int j=0;,你就不能在循环中写j=1;。但是你可以改变对象成员和数组项(所以j.x=1;j[0]=1; 可以工作)
  • 您提到了 Monte-Carlo,因此值得指出的是,随机数生成器并不是并行访问的忠实拥护者。有一个 ThreadLocalRandom.current() 调用,可为您提供每个广告的随机数生成器
  • 另外,您肯定在某处收集您的结果,并且当您明确写道大 n 不用于任何事情时,请记住,多个线程可能会尝试更新收集器数组/对象的单个位置,这可能是也可能不是问题。

【讨论】:

  • 非常感谢!!!我还有另一个问题,它来自问题中的“编辑”符号(我无法在 cmets 中写出来)。 @tevemadar
  • @YodaAndFriends 新问题应该作为一个新问题发布。这是 StackOverflow 的基本思想,每个帖子一个问题。
【解决方案2】:

使用 ThreadPoolExecutor 来排队并行执行 Runables。您可以使用 Executors 实用程序类创建一个简单的 ThreadPoolExecutor。

Executor executor = Executors.newFixedThreadPool(10); // 10 = number of threads

for (int w = 0; w < n; w++) {

    final int w_final = w;

    executor.execute(() -> {

        for (int t = 0; t < 25; t++) {
    
            for (int a = 0; a < 20; a++) {
    
               // ...calculations...
                // w_final instead of w here
   
          }
        }
    });
}

【讨论】:

    【解决方案3】:

    您想利用可用于复杂计算的计算能力,这是一个有效的方案。

    多线程优化系统资源使用并提高性能。宝贵的 CPU 时间不会被阻塞线程浪费,而是被其他线程用于执行所需的计算。

    但是没有正确性的并发没有任何意义。为了获得正确的结果,您可能需要同步并行计算(如果它们是相互关联的)。

    由于您是 Java 并发的新手,我建议您使用 Executor 框架。

    该框架由三个主要接口 Executor、ExecutorService 和 ThreadPoolExecutor 组成,抽象出大部分线程复杂性,并提供执行和管理线程生命周期的高级方法。

    让我们从一个非常基本的解决方案开始,然后您可以根据需要对其进行改进。

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    public class WorkDistributer {
        private static int THREAD_COUNT = 8;
    
        public static void main(String... args) {
    
            try {
                final ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
                int n = Integer.MAX_VALUE;
                for (int w = 0; w < n; w++) {
                    executor.execute(new Worker());
                }
                executor.awaitTermination(5, TimeUnit.MINUTES);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    class Worker implements Runnable {
        @Override
        public void run() {
            for (int t = 0; t < 25; t++) {
                for (int a = 0; a < 20; a++) {
                    // System.out.println("Doing calculation....");
                }
            }
        }
    
    }
    
    
    

    注意:

    1. 这里我们没有实现同步机制,但您可能需要根据您的要求。
    2. 根据您的系统配置调整 THREAD_COUNT。在我的系统上,我有 4 个核心和 8 个逻辑处理器,我能够使用 8 个线程实现 100% 的利用率

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-28
      • 1970-01-01
      • 2011-08-20
      • 2011-02-13
      • 1970-01-01
      • 2013-02-05
      • 2013-12-24
      • 2021-02-13
      相关资源
      最近更新 更多