强制多个线程在可用时使用多个CPU

我正在写一个Java程序,它使用了很多的CPU,因为它的性质。 但是,它可以并行运行,而且我的程序是multithreading的。 当我运行它,似乎只使用一个CPU,直到它需要更多,然后使用另一个CPU – 有什么我可以在Java中做强迫不同的线程运行在不同的核心/ CPU?

当我运行它,似乎只使用一个CPU,直到它需要更多,然后使用另一个CPU – 有什么我可以在Java中做强迫不同的线程运行在不同的核心/ CPU?

我把你的问题的这个部分解释为意味着你已经解决了使你的应用程序具有multithreading能力的问题。 尽pipe如此,它并没有立即开始使用多核心。

“有没有办法强制……”的答案是(AFAIK)不是直接的。 您的JVM和/或主机OS决定使用多less“本地”线程,以及这些线程如何映射到物理处理器。 你有一些select调整。 例如,我发现这个页面介绍如何调整Solaris上的Java线程。 这个页面谈论其他可能会减慢multithreading应用程序的事情。

在Java中有两种基本的multithreading方法。 您使用这些方法创build的每个逻辑任务应该在需要和可用时在新的核心上运行。

方法一:定义一个Runnable或Thread对象(可以在构造函数中使用一个Runnable)并使用Thread.start()方法启动它。 它会执行任何核心的操作系统给它 – 通常是较less的负载。

教程: 定义和启动线程

方法二:定义实现Runnable(如果它们不返回值)或Callable(如果它们是)接口的对象,它们包含你的处理代码。 将这些任务作为任务传递给java.util.concurrent包中的ExecutorService。 java.util.concurrent.Executors类有一堆方法来创build标准的,有用的ExecutorServicestypes。 链接到执行者教程。

从个人的经验来看,Executors固定和caching的线程池是非常好的,但你会想调整线程数。 可以在运行时使用Runtime.getRuntime()。availableProcessors()来计算可用内核。 当您的应用程序完成时,您将需要closures线程池,否则应用程序不会退出,因为ThreadPool线程保持运行。

获得良好的多核性能有时是非常棘手的,并且充满了陷阱:

  • 磁盘I / O在并行运行时减慢了一个LOT。 一次只能有一个线程读/写磁盘。
  • 对象的同步为multithreading操作提供了安全性,但会减慢工作速度。
  • 如果任务太重(小工作,快速执行),那么在ExecutorService中pipe理这些任务的开销要比从多个内核获得的开销要高。
  • 创build新的线程对象很慢。 如果可能,ExecutorServices将尝试重新使用现有的线程。
  • 当multithreading处理某些事情时,可能会发生各种各样的疯狂的事情。 保持您的系统简单,并尝试使任务逻辑上不同和不交互。

还有一个问题:控制工作很难! 一个好的做法是让一个pipe理器线程创build和提交任务,然后再与工作队列(使用ExecutorService)一起工作的线程。

我只是在触及关键点 – multithreading编程被认为是许多专家最难的编程主题之一。 它不直观,复杂,抽象往往很弱。


编辑 – 使用ExecutorService的示例:

public class TaskThreader { class DoStuff implements Callable { Object in; public Object call(){ in = doStep1(in); in = doStep2(in); in = doStep3(in); return in; } public DoStuff(Object input){ in = input; } } public abstract Object doStep1(Object input); public abstract Object doStep2(Object input); public abstract Object doStep3(Object input); public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); ArrayList<Callable> tasks = new ArrayList<Callable>(); for(Object input : inputs){ tasks.add(new DoStuff(input)); } List<Future> results = exec.invokeAll(tasks); exec.shutdown(); for(Future f : results) { write(f.get()); } } } 

首先,你应该向自己certificate你的程序在多核上运行得更快 。 许多操作系统尽可能在同一个内核上运行程序线程。

在同一个内核上运行有很多优点。 CPU高速caching很热,这意味着该程序的数据被加载到CPU中。 锁/监视器/同步对象位于CPU高速caching中,这意味着其他CPU不需要在总线上执行高速caching同步操作(昂贵!)。

有一件事情可以非常容易地让你的程序在同一个CPU上一直运行,就是过度使用锁和共享内存。 你的线程不应该彼此交谈。 线程在同一内存中使用相同对象的频率越低,它们运行在不同的CPU上的频率越高。 他们越频繁地使用相同的内存,他们越频繁地阻止等待另一个线程。

只要OS看到另一个线程的一个线程块,它就会在相同的CPU上运行该线程。 它减less了在CPU间总线上移动的内存量。 这就是我所想的是导致你在程序中看到的东西。

首先,我build议阅读Brian Goetz的“Concurrency in Practice” 。

替代文字

这是描述并发Java编程的最好的书。

并发性“易学,难掌握”。 我build议在尝试之前阅读大量有关该主题的内容。 让一个multithreading程序正常工作99.9%是非常容易的,并且失败了0.1%。 不过,以下是一些提示,帮助您开始:

有两种常见的方法可以使程序使用多个核心:

  1. 使程序运行使用多个进程。 一个例子是用Pre-Fork MPM编译的Apache,它将请求分配给subprocess。 在多进程程序中,默认情况下不会共享内存。 但是,您可以跨进程映射共享内存的部分。 Apache使用它来logging它。
  2. 使程序multithreading。 在multithreading程序中,默认情况下共享所有堆内存。 每个线程仍然有自己的堆栈,但可以访问堆的任何部分。 通常,大多数Java程序是multithreading的,而不是多进程的。

在最底层,可以创build和销毁线程 。 Java使得以便携式跨平台方式创build线程变得容易。

由于Java一直在创build和销毁线程的开销很大,所以Java现在包含了Executors来创build可重用的线程池。 可以将任务分配给执行者,并且可以通过Future对象检索结果。

通常情况下,一个任务可以分成更小的任务,但最终的结果需要重新整合。 例如,通过合并sorting,可以将列表分成更小和更小的部分,直到每个核心都进行sorting。 但是,由于每个子列表都被sorting,所以需要合并以获得最终的sorting列表。 由于这是“分而治之”的问题是相当普遍的,所以有一个JSR框架可以处理底层的分发和join。 这个框架可能会包含在Java 7中。

在Java中没有办法设置CPU关联。 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4234402

如果您必须这样做,请使用JNI创build本机线程并设置其亲和力。

您应该编写程序以Callable的forms交给ExecutorService执行,并使用invokeAll(…)执行。

然后,您可以从Executors类在运行时select合适的实现。 一个build议是要调用Executors.newFixedThreadPool()与一个数量大致相当的CPU核心数保持繁忙。

最简单的事情是把你的程序分成多个进程。 操作系统将在核心之间分配它们。

比较困难的是把你的程序分解成多个线程,并相信JVM能正确地分配它们。 这通常是人们利用可用硬件所做的。


编辑

一个多处理程序如何“更容易”? 这是pipe道中的一个步骤。

 public class SomeStep { public static void main( String args[] ) { BufferedReader stdin= new BufferedReader( System.in ); BufferedWriter stdout= new BufferedWriter( System.out ); String line= stdin.readLine(); while( line != null ) { // process line, writing to stdout line = stdin.readLine(); } } } 

pipe道中的每一步都是相似的结构。 包括了9行的开销。

这可能不是绝对最有效的。 但是很简单


并发进程的整体结构不是JVM问题。 这是一个操作系统问题,所以使用shell。

 java -cp pipline.jar FirstStep | java -cp pipline.jar SomeStep | java -cp pipline.jar LastStep 

剩下的唯一事情就是为stream水线中的数据对象制定一些序列化。 标准序列化运作良好。 阅读http://java.sun.com/developer/technicalArticles/Programming/serialization/关于如何序列化的提示。; 你可以使用ObjectInputStreamObjectOutputStreamreplaceBufferedReaderBufferedWriter来完成这个任务。

我认为这个问题与Java并行处理框架(JPPF)有关。 使用这个你可以在不同的处理器上运行不同的工作。

为什么这个Java代码不使用所有的CPU核心? 。 请注意,这仅适用于JVM,因此您的应用程序必须已经使用线程(并且多less有点“正确”):

sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.html

您可以使用Java 8以下版本的Executors中的API

 public static ExecutorService newWorkStealingPool() 

使用所有可用的处理器创build工作线程池作为其目标并行级别。

由于工作窃取机制,空闲线程从繁忙线程的任务队列中窃取任务,整体吞吐量将增加。

从grepcode中 , newWorkStealingPool的实现如下

 /** * Creates a work-stealing thread pool using all * {@link Runtime#availableProcessors available processors} * as its target parallelism level. * @return the newly created thread pool * @see #newWorkStealingPool(int) * @since 1.8 */ public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }