根据CPU核心数如何扩展线程规模?

112

我想在Java中使用多线程来解决一个数学问题。我的数学问题可以分成多个工作单元,在多个线程中解决。

我不想固定线程数量,而是希望线程数量匹配CPU核心数量。我的问题是,我在互联网上找不到简单的教程。我找到的所有示例都使用了固定数量的线程。

这该怎么做?您能提供一些示例吗?

6个回答

123
您可以使用静态的Runtime方法availableProcessors来确定Java虚拟机可用的进程数。一旦您确定了可用处理器的数量,就创建相应数量的线程并根据需要分配工作。 更新:为了进一步澄清,线程在Java中只是一个对象,因此您可以像创建其他任何对象一样创建它。假设您调用上述方法并发现它返回2个处理器。太棒了。现在,您可以创建一个循环,生成一个新的线程,将工作分离给该线程,并启动该线程。以下是一些伪代码以演示我的意思:
int processors = Runtime.getRuntime().availableProcessors();
for(int i=0; i < processors; i++) {
  Thread yourThread = new AThreadYouCreated();
  // You may need to pass in parameters depending on what work you are doing and how you setup your thread.
  yourThread.start();
}

如果您想了解有关创建自己的线程的更多信息,请查看此教程。另外,您可能还需要查看线程池来创建线程。


20
基本上是正确的,但要注意在市场上推广了Intel的“超线程”处理器上的性能。在四核处理器上,这将返回8而不是4,但实际上,在使用超过4个线程后性能可能会下降-这是我的基准测试告诉我的 :) - xcut
嗨,好的,我不知道这是可能的。 但是当我将一个任务分成几个工作单元,并且我需要所有部分解决方案用于最终工作步骤时,该怎么办?当我有几个“yourThreads”时,如何使用join()进行操作,因为我看不到这些线程是可区分的?:)顺便说一句:您提供的线程池链接将我带到了http://www.ibm.com/developerworks/library/j-jtp0730.html :) - Andreas Hornig
5
看一下这个例子:http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ExecutorService.html 它会告诉你如何更加高效地创建和管理线程池... 刚开始可能会比较复杂,但通常情况下,它之所以更加复杂是因为如果它变得更简单,你会很快遇到限制。 - Bill K
奇怪,我使用1000设置了我的newFixedThreadPool,它的表现比availableProcessors好得多。这是如何实现的?在超过可用CPU核心数量的线程上如何工作? - wilmol

67

你可能也想看看 java.util.concurrent 框架来处理这些事情。

ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// Do work using something like either
e.execute(new Runnable() {
        public void run() {
            // do one task
        }
    });
或者
    Future<String> future = pool.submit(new Callable<String>() {
        public String call() throws Exception {
            return null;
        }
    });
    future.get();  // Will block till result available

这比自己处理线程池等要好得多。


嗨,DaveC, 嗯,以前不知道这个,所以我会看一下。它可以根据可用的 CPU 核心进行扩展吗?因为我在你的简短示例中没有看到。 最好的问候,安德烈亚斯 - Andreas Hornig
3
java.util.concurrent 非常可扩展。 - Kristopher Ives
4
一个固定大小的池,池中可用处理器的数量通常对于CPU密集型进程是最优的。这里的第一个例子就是你需要做的全部内容。 - Peter Lawrey
1
正如被接受的答案的第一条评论所述,最好使用报告的“处理器”数量的一半,原因有两个:1.如果您有超线程,则实际处理器数量是报告数量的一半,2.它为系统的其余部分(操作系统和其他程序)留下了一些处理能力。 - Matthieu

12

选项1:

Executors中使用newWorkStealingPool

public static ExecutorService newWorkStealingPool()

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

通过此 API,您无需将核心数传递给 ExecutorService

此 API 的实现来自于 grepcode

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

选项2:

Executors类提供的newFixedThreadPool API或其他newXXX构造函数,可返回ExecutorService

public static ExecutorService newFixedThreadPool(int nThreads)

Runtime.getRuntime().availableProcessors()替换nThreads

选项3:

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue)

Runtime.getRuntime().availableProcessors()作为参数传递给maximumPoolSize


9

7

标准的方法是使用Runtime.getRuntime().availableProcessors()。 在大多数标准CPU上,这将返回最佳线程数(实际上不是CPU核心数)。因此,这就是您要寻找的内容。

例如:

ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

不要忘记像这样关闭执行器服务(否则你的程序将无法退出):

service.shutdown();

这里只是简要概述如何设置基于未来的机器翻译代码(离题,作为说明):

CompletionService<YourCallableImplementor> completionService = 
    new ExecutorCompletionService<YourCallableImplementor>(service);
    ArrayList<Future<YourCallableImplementor>> futures = new ArrayList<Future<YourCallableImplementor>>();
    for (String computeMe : elementsToCompute) {
        futures.add(completionService.submit(new YourCallableImplementor(computeMe)));
    }

那么您需要追踪您预期的结果数量,并像这样检索它们:
try {
  int received = 0;
  while (received < elementsToCompute.size()) {
     Future<YourCallableImplementor> resultFuture = completionService.take(); 
     YourCallableImplementor result = resultFuture.get();
     received++; 
  }
} finally {
  service.shutdown();
}

2
关闭调用应该放在try finally中。 - Christophe Roussy
1
@ChristopheRoussy,你说得很对,我已经相应地修改了代码片段,谢谢! - fl0w

4

在Runtime类中,有一个名为availableProcessors()的方法。您可以使用它来确定您有多少个CPU。由于您的程序是CPU绑定的,您可能希望每个可用CPU最多只有一个线程。


嗨,Jason和Eric(我使用一个评论来回答你们两个的问题,因为它基本上是相同的)。好的,检查这个很不错,但这将是第一部分。当我有了核心数,我必须把线程作为变量,就像这个核心数量一样。 我之前尝试过这个例子http://openbook.galileodesign.de/javainsel5/javainsel09_003.htm#Rxx747java09003040002E31F0491F9(德语!),它使用了一个固定的线程。但我想在双核环境中使用2个核心,在四核环境中使用4个核心进行相同的编程。我不想手动更改它。 这可能吗?谢谢! :) - Andreas Hornig
@Andreas - 请查看我在帖子中所做的更新。我认为那会有助于澄清问题。 - JasCav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接