将 for 循环分离为多线程 - Java

Separate a for loop into multithreaded - Java

我必须使用 Java 进行 Monte Carlo 模拟。我处于以下情况:

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
  }
}

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

使用 ThreadPoolExecutor 排队并行执行 Runable。您可以使用 Executors 实用程序 class.

创建一个简单的 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
   
          }
        }
    });
}

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

多线程优化了系统资源的使用,提高了性能。宝贵的 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% 的利用率

使用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; 会起作用)
  • 您提到了蒙特卡洛,因此可能值得指出 运行dom 数字生成器不是并行访问的忠实拥护者。有一个 ThreadLocalRandom.current() 调用可以为每个 thead
  • 提供一个 运行dom 号码生成器
  • 此外,您肯定会在某处收集结果,并且当您明确写道大 n 没有用于任何用途时,请记住多个线程可能会尝试更新收集器的单个位置 array/object,这可能是也可能不是问题。