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

时间:2009-08-03 15:43:56

标签: java concurrency multithreading multicore

我正在编写一个使用大量CPU的Java程序,因为它的功能本质。但是,很多都可以并行运行,而且我的程序是多线程的。当我运行它时,它似乎只使用一个CPU,直到它需要更多然后它使用另一个CPU - 我可以用Java做什么来强制不同的线程在不同的核心/ CPU上运行?

10 个答案:

答案 0 :(得分:56)

Java中有多种基本的多线程方法。使用这些方法创建的每个逻辑任务都应该在需要和可用时在新核心上运行。

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

教程:Defining and Starting Threads

方法二:定义实现Runnable的对象(如果它们不返回值)或Callable(如果它们)接口,它们包含您的处理代码。将这些作为任务从java.util.concurrent包传递给ExecutorService。 java.util.concurrent.Executors类有许多方法来创建标准的,有用的ExecutorServices类。 Link到Executors教程。

根据个人经验,执行人员已经确定并且虽然你想要调整线程数,但缓存的线程池非常好。可以在运行时使用Runtime.getRuntime()。availableProcessors()来计算可用内核。应用程序完成后,您需要关闭线程池,否则应用程序将不会退出,因为ThreadPool线程仍在运行。

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

  • 运行时,磁盘I / O会减慢LOT 平行。一次只能有一个线程进行磁盘读/写。
  • 对象的同步为多线程操作提供了安全性,但减慢了工作速度。
  • 如果任务也是如此 琐碎的(小工作位,执行 快速)管理它们的开销 在ExecutorService中的成本超过 你从多个核心中获益。
  • 创建新的Thread对象很慢。如果可能,ExecutorServices将尝试重用现有线程。
  • 当多个线程处理某些事情时,会发生各种各样的疯狂事情。保持系统简单,并尝试使任务在逻辑上不同且不相互作用。

另一个问题:控制工作很难!一个好的做法是让一个管理器线程创建并提交任务,然后是一些工作线程与工作队列(使用ExecutorService)。

我只是在谈论关键点 - 多线程编程被许多专家认为是最难编程的主题之一。它不直观,复杂,抽象通常很弱。


编辑 - 使用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());
        }
    }
}

答案 1 :(得分:29)

  

当我运行它时,似乎只能使用它   一个CPU,直到它需要更多它   使用另一个CPU - 有什么我的   可以用Java来强制不同   线程在不同的运行   核/ CPU的?

我将您问题的这一部分解释为您已经解决了使您的应用程序具有多线程能力的问题。尽管如此,它并没有立即开始使用多个核心。

“有没有办法强迫......”的答案是(AFAIK)不直接。您的JVM和/或主机操作系统决定使用多少“本机”线程,以及这些线程如何映射到物理处理器。你有一些调整选项。例如,我发现this page讨论了如何调整Solaris上的Java线程。 this page讨论了可能减慢多线程应用程序速度的其他因素。

答案 2 :(得分:18)

首先,您应该向自己证明您的程序将在多个核心上运行更快。许多操作系统都尽可能地在同一个核心上运行程序线程

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

有一件事可以很容易地使你的程序始终在同一个CPU上运行,这是过度使用锁和共享内存。你的线程不应该互相交谈。线程在同一内存中使用相同对象的频率越低,它们在不同CPU上运行的频率就越高。他们使用相同内存的次数越多,他们就越需要阻止等待另一个线程。

每当操作系统看到另一个线程的一个线程块时,它就会在同一个CPU上运行该线程。它减少了在CPU间总线上移动的内存量。这就是我猜你在程序中看到的结果。

答案 3 :(得分:8)

首先,我建议阅读"Concurrency in Practice" by Brian Goetz

alt text

这是迄今为止描述并发java编程的最佳书籍。

并发性“易于学习,难以掌握”。在尝试之前,我建议阅读有关该主题的大量内容。很容易让多线程程序在99.9%的时间内正常工作,并且失败率为0.1%。但是,这里有一些提示可以帮助您入门:

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

  1. 使用多个进程运行程序。一个例子是使用Pre-Fork MPM编译的Apache,它将请求分配给子进程。在多进程程序中,默认情况下不共享内存。但是,您可以跨进程映射共享内存的各个部分。 Apache用它的“记分牌”来做到这一点。
  2. 使程序多线程。在多线程程序中,默认情况下共享所有堆内存。每个线程仍然拥有自己的堆栈,但可以访问堆的任何部分。通常,大多数Java程序都是多线程的,而不是多进程的。
  3. 在最低级别,可以create and destroy threads。 Java使得以便携式跨平台方式创建线程变得容易。

    由于创建和销毁线程的成本往往很高,Java现在包含Executors来创建可重用的线程池。可以将任务分配给执行程序,并且可以通过Future对象检索结果。

    通常,人们有一项任务可以分成较小的任务,但最终结果需要重新组合在一起。例如,使用合并排序,可以将列表划分为越来越小的部分,直到每个核心都进行排序。但是,由于每个子列表都已排序,因此需要将其合并以获取最终的排序列表。由于这是“分而治之”的问题相当普遍,因此有JSR framework可以处理基础分布和加入。该框架可能会包含在Java 7中。

答案 4 :(得分:4)

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

如果必须这样做,请使用JNI创建本机线程并设置它们的亲和力。

答案 5 :(得分:1)

最简单的方法是将程序分解为多个进程。操作系统将在核心之间分配它们。

将程序分解为多个线程并信任JVM以正确分配它们有点困难。这通常是人们使用可用硬件所做的事情。


修改

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

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();
        }
    }
}

管道中的每个步骤都是类似的结构。包括任何处理的9行开销。

这可能不是绝对最有效的。但这很容易。


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

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

唯一剩下的就是为管道中的数据对象计算一些序列化。 标准序列化效果很好。请阅读http://java.sun.com/developer/technicalArticles/Programming/serialization/以获取有关如何序列化的提示。您可以将BufferedReaderBufferedWriter替换为ObjectInputStreamObjectOutputStream来完成此操作。

答案 6 :(得分:1)

你应该编写你的程序,以Callable的 lot 的形式完成它的工作,交给ExecutorService并用invokeAll(...)执行。

然后,您可以从Executors类中在运行时选择合适的实现。建议将Executors.newFixedThreadPool()调用一个大致对应于cpu核心数的数字来保持忙碌。

答案 7 :(得分:1)

我认为这个问题与Java Parallel Proccesing Framework(JPPF)有关。使用它,您可以在不同的处理器上运行不同的作业。

答案 8 :(得分:1)

之前在Why does this Java code not utilize all CPU cores?中提到过JVM性能调优。请注意,这仅适用于JVM,因此您的应用程序必须已经使用线程(并且或多或少“正确”):

http://ch.sun.com/sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.pdf

答案 9 :(得分:1)

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

public static ExecutorService newWorkStealingPool()
  

使用所有可用处理器作为目标并行级别创建工作窃取线程池。

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

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