如何在java中安全地使用并行流?
我在一个大型计算中使用了并行流,但是当我执行它时,结果很奇怪。当在纯[SEQ]进程调度中执行所有操作时,它工作得很好 为了找到问题,我使用了一个简单的计算,工作是将从“迭代次数”到零的所有自然数相加:如何在java中安全地使用并行流?,java,parallel-processing,Java,Parallel Processing,我在一个大型计算中使用了并行流,但是当我执行它时,结果很奇怪。当在纯[SEQ]进程调度中执行所有操作时,它工作得很好 为了找到问题,我使用了一个简单的计算,工作是将从“迭代次数”到零的所有自然数相加: private long sum = 0; // global helper public static void main(String args[]) { int iterations = 50000; // the targe
private long sum = 0; // global helper
public static void main(String args[])
{
int iterations = 50000; // the target number
// solving the task parallel
// ---------------------------------- <Section-under-Test>.START
final long startPAR = System.nanoTime();
IntStream.range(1, iterations+1).parallel().forEach(i->{
sum += i;
});
final long durationPAR = ( ( System.nanoTime() - startPAR ) / 1_000_000 );
// ---------------------------------- <Section-under-Test>.FINISH
long sumParallel = sum; sum = 0; // save + reset variable
System.out.println( "Sum parallel: " + sumParallel
+ " TOOK: " + durationPAR
);
// solving the task linear using one core
// ---------------------------------- <Section-under-Test>.START
final long startSEQ = System.nanoTime();
for(int i = 1; i < iterations+1; i++)
{
sum += i;
}
final long durationSEQ = ( ( System.nanoTime() - startSEQ ) / 1_000_000 );
// ---------------------------------- <Section-under-Test>.FINISH
System.out.println( "Sum serial: " + sum
+ " TOOK: " + durationSEQ
);
System.out.println((sum == sumParallel));
}
所以我想知道的是:
为什么会这样?
如何预防呢?
我是否没有抓住要点,想在错误的地方使用并行计算
信息:
在我更大的计算中,我计算的是这个值,在这个例子中,这个值加在和上。这样就可以了。但是我如何正确地将这个结果添加到全局变量中呢?因为sum+=I;不是原子的,它涉及多个操作:
读我
读和
计算和加i
将结果分配给sum
在执行这些操作的任何时候,我们都有其他线程执行相同的操作。如果两个线程同时读取求和运算2,则肯定会得到不同的结果,因为当它们都计算求和+i时,两个线程都没有考虑另一个线程的加法
如果您想并行进行这样的计算,请使用。像addAndGet这样的操作保证是原子的,也就是说,它保证在一个步骤中发生,而不是分解成上面的步骤。因为sum+=i;不是原子的,它涉及多个操作:
读我
读和
计算和加i
将结果分配给sum
在执行这些操作的任何时候,我们都有其他线程执行相同的操作。如果两个线程同时读取求和运算2,则肯定会得到不同的结果,因为当它们都计算求和+i时,两个线程都没有考虑另一个线程的加法
如果您想并行进行这样的计算,请使用。addAndGet之类的操作保证是原子的,也就是说,它保证在一个步骤中发生,并且不会分解为上述步骤。只需使用.sum方法,这样就不会有线程在共享状态下运行,因此不需要同步或原子访问:
int sum = IntStream.rangeClosed(1, iterations).parallel().sum();
如果需要对计算进行更多控制,可以使用.reduce方法:
只要使用.sum方法,就不会有线程在共享状态下运行,因此不需要同步或原子访问:
int sum = IntStream.rangeClosed(1, iterations).parallel().sum();
如果需要对计算进行更多控制,可以使用.reduce方法:
非常整洁,谢谢。另一个小问题:是否有类似原子双精度的东西?对于您的用例来说,似乎是一个很好的解决方案。非常整洁,谢谢。另一个小问题:是否有类似AtomicDouble的东西?对于您的用例,这似乎是一个很好的解决方案。您是否介意为每个测试运行使用System.nanoTime添加测量的持续时间?不仅要测量结果的正确性,而且要测量使用[SEQ]v/s[PAR]的成本,因为[SEQ]v/s[PAR]是由并行流方法和过程调度策略实现的。其中一个方面的差异已经让您感到惊讶,但在专业软件工程设计实践中,不阅读第二个方面更是一种罪过。您是否愿意使用计时+发布结果[SEQ]和[PAR]持续时间来重新运行测试?谢谢。@user3666197当然,这是我第二件要做的事。事实上,与PAR相比,Seq的运行速度明显快了12秒到7秒,当然速度更快。原因很多。好吧,我的许多帖子都得到了很多歇斯底里的否决票,这纯粹是一种愤怒的惩罚,一次解释,为什么在InMOS硬件并行Transputers时代之后,[PAR]的附加成本实际上很多次都不是真的-[parallel],而是一个公正的-[CONCURRENT]进程调度更重要,比任何简单的输入和使用这样的语法构造函数。。。有兴趣吗?>>您是否介意在已经添加了测量值的地方重新运行上述更新的代码,而不是.sum或.reduce方法,因为即使是错误的[PAR]结果实际上也可能比纯[SEQ]方法花费更多的成本-处理并发布更新后的精确数字以及您执行的CPU内核数量,甚至可能尝试添加一些预热+多次测量循环,以过滤一些系统执行噪音,但硬事实很重要,以便看到整个画面清晰、可靠,对吗?您是否介意使用System.nanoTime为每个测试运行添加测量的持续时间?不仅要测量结果的正确性,而且要测量使用[SEQ]v/s[PAR]的成本,因为[SEQ]v/s[PAR]是由并行流方法和过程调度策略实现的。其中一个方面的差异已经让您感到惊讶,但在专业软件工程设计实践中,不阅读第二个方面更是一种罪过。您是否愿意使用计时+发布结果[SEQ]和[PAR]持续时间来重新运行测试?谢谢。@user3666197当然,这是我第二件要做的事。事实上,它是图尔
与PAR相比,NED的速度明显快于12秒,而不是7秒。当然,速度更快。原因很多。好吧,我的许多帖子都得到了很多歇斯底里的否决票,这纯粹是一种愤怒的惩罚,一次解释,为什么在InMOS硬件并行Transputers时代之后,[PAR]的附加成本实际上很多次都不是真的-[parallel],而是一个公正的-[CONCURRENT]进程调度更重要,比任何简单的输入和使用这样的语法构造函数。。。有兴趣吗?>>您是否介意在已经添加了测量值的地方重新运行上述更新的代码,而不是.sum或.reduce方法,因为即使是错误的[PAR]结果实际上也可能比纯[SEQ]方法花费更多的成本-处理并发布更新后的精确数字以及您执行的CPU内核数量,甚至可能尝试添加一些预热+多次测量循环,以过滤一些系统执行噪音,但硬事实很重要,以便看到整个画面清晰和声音,对吗?
int sum = IntStream.rangeClosed(1, iterations).parallel().reduce(0, (a, b) -> a + b);