代码之家  ›  专栏  ›  技术社区  ›  Nosrama

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

  •  66
  • Nosrama  · 技术社区  · 17 年前

    我正在编写一个Java程序,它使用很多CPU,因为它的本质是什么。但是,很多程序可以并行运行,我已经使我的程序多线程化了。当我运行它,它似乎只使用一个CPU,直到它需要更多,然后它使用另一个CPU -有什么我可以做的Java,迫使不同的线程运行在不同的核/ CPU?

    10 回复  |  直到 10 年前
        1
  •  29
  •   Stephen C    17 年前

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

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

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

        2
  •  55
  •   BobMcGee    17 年前

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

    方法一: 定义一个可运行的或线程对象(它可以在构造函数中获得可运行的),并用thread.start()方法启动它。它将在操作系统提供的任何核心上执行——通常是负载较低的核心。

    辅导的: Defining and Starting Threads

    方法二: 定义实现可运行(如果不返回值)或可调用(如果返回值)接口的对象,其中包含处理代码。将这些任务从java.util.concurrent包传递给executorService。java.util.concurrent.executors类有许多方法来创建标准的、有用的executorservices类型。 Link 执行者教程。

    根据个人经验,执行器的固定和缓存线程池非常好,尽管您希望调整线程计数。run time.getRuntime().availableProcessors()可在运行时用于计算可用核心。当应用程序完成后,您需要关闭线程池,否则应用程序将不会退出,因为线程池线程仍在运行。

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

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

    另一个问题是:控制工作很难!一个好的实践是拥有一个创建和提交任务的管理器线程,然后拥有两个带有工作队列的工作线程(使用ExecutorService)。

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


    edit——使用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());
            }
        }
    }
    
        3
  •  17
  •   Zan Lynx    15 年前

    首先,你应该向自己证明你的程序可以运行 更快 在多个核心上。许多操作系统努力在同一个核心上运行程序线程。 如果可能的话 .

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

    有一件事很容易让你的程序一直在同一个CPU上运行,那就是过度使用锁和共享内存。你的线程不应该互相通信。线程在同一内存中使用相同对象的频率越低,它们在不同CPU上运行的频率就越高。它们使用相同内存的频率越高,就越需要阻塞等待另一个线程。

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

        4
  •  8
  •   brianegge    17 年前

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

    alt text

    这是迄今为止描述并发Java编程的最好的书。

    并发性是“容易学习,难掌握”。我建议在尝试之前多读一些关于这个主题的文章。很容易让多线程程序在99.9%的时间内正常工作,并且失败0.1%。不过,以下是一些让您开始学习的提示:

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

    1. 使用多个进程运行程序。一个例子是使用前叉MPM编译的Apache,它将请求分配给子进程。在多进程程序中,默认情况下不共享内存。但是,您可以跨进程映射共享内存的各个部分。阿帕奇用它的“记分板”来做这个。
    2. 使程序多线程。在多线程程序中,默认情况下共享所有堆内存。每个线程仍然有自己的堆栈,但可以访问堆的任何部分。通常,大多数Java程序是多线程的,而不是多进程的。

    在最底层,一个人可以 create and destroy threads . Java使得以可移植的跨平台方式创建线程变得很容易。

    因为它往往会花费昂贵的时间来创建和销毁线程,Java现在包括 Executors 创建可重用的线程池。任务可以分配给执行者,结果可以通过未来的对象来检索。

    通常情况下,一个人有一个任务可以分为较小的任务,但最终的结果需要放在一起。例如,使用合并排序,可以将列表分成越来越小的部分,直到每个核心都进行排序。但是,由于每个子列表都是经过排序的,所以需要合并才能得到最终排序的列表。由于这是“分而治之”的问题相当普遍,因此 JSR framework 它可以处理底层的分布和连接。这个框架很可能包含在Java 7中。

        5
  •  4
  •   Iouri Goussev    16 年前

    在Java中没有设置CPU亲和力的方法。 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4234402

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

        6
  •  1
  •   Thorbjørn Ravn Andersen    17 年前

    你应该以一种 许多 将callable交给ExecutorService并使用invokeAll(…)执行。

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

        7
  •  1
  •   S.Lott    17 年前

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

    更难的是将程序分解为多个线程,并信任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/ 有关如何序列化的提示。您可以替换 BufferedReader BufferedWriter 具有 ObjectInputStream ObjectOutputStream 为了完成这一点。

        8
  •  1
  •   Nandika    15 年前

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

        9
  •  1
  •   Community Mohan Dere    9 年前

    前面在 Why does this Java code not utilize all CPU cores? . 请注意,这只适用于JVM,因此您的应用程序必须已经在使用线程(在这一点上或多或少是“正确的”):

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

        10
  •  1
  •   Ravindra babu    10 年前

    您可以使用下面的API Executors 用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);
        }
    
    推荐文章