繁体   English   中英

(Java中的线程池)增加线程数会导致简单的for循环减慢速度。 为什么?

[英](Thread pools in Java) Increasing number of threads creates slow down for simple for loop. Why?

我有一些可以轻松并行化的工作,我想使用Java线程将工作分散到我的四核机器上。 这是一种应用于旅行商问题的遗传算法。 它听起来不容易并行化,但第一个循环非常容易。 我谈论实际演变的第二部分可能是也可能不是,但我想知道我是否因为我实现线程的方式而变慢,或者算法本身。

此外,如果有人对如何实施我想要做的事情有更好的想法,那将非常感激。

在main()中,我有这个:

 final ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(numThreads*numIter);
 ThreadPoolExecutor tpool = new ThreadPoolExecutor(numThreads, numThreads, 10, TimeUnit.SECONDS, queue);
 barrier = new CyclicBarrier(numThreads);
 k.init(tpool);

我有一个在init()内部完成的循环,如下所示:

for (int i = 0; i < numCities; i++) {
    x[i] = rand.nextInt(width);
    y[i] = rand.nextInt(height);
}

我改变了这个:

int errorCities = 0, stepCities = 0;
stepCities = numCities/numThreads;
errorCities = numCities - stepCities*numThreads;

// Split up work, assign to threads                                                                        
for (int i = 1; i <= numThreads; i++) {
    int startCities = (i-1)*stepCities;
    int endCities = startCities + stepCities;

    // This is a bit messy...                                                                              
    if(i <= numThreads) endCities += errorCities;
    tpool.execute(new citySetupThread(startCities, endCities));
}

这里是citySetupThread()类:

public class citySetupThread implements Runnable {
    int start, end;

    public citySetupThread(int s, int e) {
        start = s;
        end = e;
    }
    public void run() {
        for (int j = start; j < end; j++) {
            x[j] = ThreadLocalRandom.current().nextInt(0, width);
            y[j] = ThreadLocalRandom.current().nextInt(0, height);
        }

        try {
            barrier.await();
        } catch (InterruptedException ie) {
            return;
        } catch (BrokenBarrierException bbe) {
            return;
        }
    }
}

上面的代码在程序中运行一次,因此它对我的线程构造来说是一种测试用例(这是我第一次使用Java线程)。 我在一个真正的关键部分实现了同样的东西,特别是遗传算法的演化部分,其类如下:

public class evolveThread implements Runnable {
    int start, end;

    public evolveThread(int s, int e) {
        start = s;
        end = e;
    }
    public void run() {
        // Get midpoint                                                                                            
        int n = population.length/2, m;

        for (m = start; m > end; m--) {
            int i, j;
            i = ThreadLocalRandom.current().nextInt(0, n);

            do {
                j = ThreadLocalRandom.current().nextInt(0, n);
            } while(i == j);

            population[m].crossover(population[i], population[j]);
            population[m].mutate(numCities);
        }

        try {
            barrier.await();
        } catch (InterruptedException ie) {
            return;
        } catch (BrokenBarrierException bbe) {
            return;
        }

    }
}

它存在于函数evolve()中,在init()中调用,如下所示:

for (int p = 0; p < numIter; p++) evolve(p, tpool);

是的,我知道这不是一个非常好的设计,但由于其他原因,我坚持使用它。 evolve的内部是相关部分,如下所示:

// Threaded inner loop                                                                                     
int startEvolve = popSize - 1,
endEvolve = (popSize - 1) - (popSize - 1)/numThreads;

// Split up work, assign to threads                                                                        
for (int i = 0; i < numThreads; i++) {
    endEvolve = (popSize - 1) - (popSize - 1)*(i + 1)/numThreads + 1;
    tpool.execute(new evolveThread(startEvolve, endEvolve));
    startEvolve = endEvolve;
}

// Wait for our comrades                                                                                   
try {
     barrier.await();
} catch (InterruptedException ie) {
     return;
} catch (BrokenBarrierException bbe) {
     return;
}

population[1].crossover(population[0], population[1]);
population[1].mutate(numCities);
population[0].mutate(numCities);

// Pick out the strongest                                                                                      
Arrays.sort(population, population[0]);
current = population[0];
generation++;

我真正想知道的是:

  • “队列”有什么作用? 我是否正确为我认为将为池中的所有线程执行的多个作业创建队列? 如果大小不够大,我会得到RejectedExecutionException。 我刚刚决定做numThreads * numIterations,因为那将是多少工作(对于我之前提到的实际演化方法)。 这很奇怪..如果barrier.await()的工作正常,我不应该这样做,这导致我......

  • 我正确使用barrier.await()吗? 目前我有两个地方:Runnable对象的run()方法内部,以及执行所有作业的for循环之后。 我认为只需要一个,但如果我删除其中一个,我会收到错误。

  • 我怀疑线程的争用,因为这是我唯一可以从荒谬的减速中收集的东西(它确实与输入参数一起缩放)。 我想知道它是否与我如何实现线程池和障碍有关。 如果没有,那么我想我必须查看crossover()和mutate()方法。

首先,我想你可能有一个关于如何使用CyclicBarrier的错误。 目前,您正在使用执行程序线程数作为参与方数来初始化它。 但是,你还有一个派对; 主线程。 所以我认为你需要这样做:

barrier = new CyclicBarrier(numThreads + 1);

我认为这应该有效,但我个人觉得这个障碍很奇怪。

当使用工作队列线程池模型时,我发现使用Semaphore或Java的Future模型更容易。

对于信号量:

class MyRunnable implements Runnable {
  private final Semaphore sem;

  public MyRunnable(Semaphore sem) {
    this.sem = sem;
  }

  public void run() {
    // do work

    // signal complete
    sem.release()
  }
}

然后在你的主线程中:

Semaphore sem = new Semaphore(0);

for (int i = 0; i < numJobs; ++i) {
  threadPool.execute(new MyRunnable(sem));
}

sem.acquire(numJobs);

它确实做了与屏障相同的事情,但我发现更容易思考工作任务“发出信号”它们已完成,而不是再次与主线程“同步”。

例如,如果查看CyclicBarrier JavaDoc中的示例代码,对barrier.await()的调用就在worker内部的循环内部。 因此,它实际上正在同步多个长时间运行的工作线程,并且主线程没有参与屏障。 在循环外的worker的末尾调用barrier.await()更多信号完成。

随着任务数量的增加,使用每个任务添加会增加开销。 这意味着您希望最小化任务数量,即与您拥有的cpu数量相同。 对于使用double的一些任务,当工作负载不均匀时,cpus的数量会更好。

顺便说一句:你不需要在每个任务中都有障碍,你可以通过在每个任务上调用get()来等待每个任务的未来完成。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM