以负载均衡方式在 4 个线程上分配 10 个无限作业 (Java)

Distribute 10 infinite jobs over 4 threads in a load-balanced manner (Java)

我有 10 个计算作业 需要(接近)无限时间。例如:计算PI的下一位,solve一个NP-hard约束满足问题等

我有 4 个线程(所以在 8 个内核 的机器上有一个 4 个线程的线程池,所以我还剩下一些内核避免实时锁定机器和进程)。

使用 Java8,如何在这 4 个线程中分配这 10 个作业?

这是个坏主意:

ExecutorService es = Executors.newFixedThreadPool(4);
for (Job j : jobs) {
    es.submit(j);
}

因为 4 个作业将开始,但 none 将完成,因此作业 5-10 永远不会开始。

如果我照看 10 分钟,我希望每个作业有 运行 大约 4 分钟。 20 分钟后,每个作业有 运行 大约 8 分钟,等等。处理这个问题的典型模式是什么? (如果需要,我可以实现一种在预设时间后暂停计算的方法。)

如果您需要并行执行 10 个作业 - 只需 运行 10 个线程。

Executors.newFixedThreadPool(4) 更改为 Executors.newFixedThreadPool(10)

我认为您正在寻找 WorkStealingPool:

static ExecutorService executor = Executors.newWorkStealingPool(4);
private static Map<Integer, AtomicInteger> map = new ConcurrentHashMap<>();

public static void main(String[] args) throws InterruptedException {


    for (int i = 0; i < 10; i++) {
        executor.submit(new Worker(i))  ;
    }

    Thread.sleep(10000);
    System.out.println(map);
}

private static class Worker implements Runnable {
    private final int k;

    public Worker(int k) {
        this.k = k;
    }

    @Override
    public void run() {
        map.putIfAbsent(k, new AtomicInteger(0));
        map.get(k).getAndIncrement();
        executor.submit(new Worker(this.k));

       // Also possible to resubmit current job
       //executor.submit(this);
    }
}

我对 "Job that never finishes." 的想法有些困扰,我会称它为其他名称,例如 "long-running computation" 或...

如果你有十个,而你只能负担四个线程来处理它们,那么你唯一的选择就是将它们分解成有限的 "sub-jobs" do 完成,然后编写一个调度程序,不断向四个可用线程提供子作业。

但这将复制线程系统应该为您完成的大部分工作。

我只做十个线程。如果您 运行ning 在只有四个可用核心的机器上 运行 十个线程,OS 会自动将您的长 运行ning 作业分解为 "sub-jobs"(即时间片),并在四核上公平调度。

在四个线程之间分配十个作业的任务和仅利用四个CPU的任务(我这里使用CPU作为核心的同义词 为了简单起见)你的十个工作有点不同。

四个线程

将线程数限制为四个并不能保证它们会坚持使用四个 CPU 并且不会使用其他线程。 OS 可以根据需要在所有可用的 CPU 之间随机播放您的线程。您唯一可以保证的是,您的程序将无法利用所有 CPU 资源的 50% 以上(假设您有八个 CPU)。

但您不太可能设法利用这 50%。尽管您的作业主要是面向 CPU 的,但它们仍然可能需要不时地读取和写入内存。当一个线程在这样的 readings/writings 上错过了缓存并等待数据被传送到处理器时,这个处理器会暂停该线程并可以在另一个线程中做一些工作。在您的情况下,它将无事可做,只是闲置直到数据到达。因此,您的 CPU 很可能未得到充分利用。

如果你决定采用这种方法,你需要将你的工作分解成小任务并将它们提供给执行者,正如@James Large 所说。您可以将 WorkStealingPool 与四个线程一起使用(正如@Alexey Soshin 所建议的那样),或者创建一个具有十个线程的池并使用 Semaphore 并使用四个许可并将公平设置为 true。在后一种情况下,您的线程必须使用循环,在每次迭代开始时获取许可并在结束时释放它们。每次迭代将代表一小部分工作。

四个CPUs

有一些机制可以指定特定的 CPU 来处理您的任务。

在 Linux 中的进程级别,您可以使用 special commands 将您的进程绑定到特定的 CPU。这将允许您创建十个线程并让 OS 在四个 CPU 上完成所有平衡。

在线程级别,您可以尝试 Java Affinity library 来自 OpenHFT。它允许在您的 Java 代码中将线程绑定到 CPUs。问题是十个线程不能在没有提醒的情况下被分配到四个CPU之间,所以很难平衡它们。