【问题标题】:More than 2 threads working slower than 1 or 2 threads unless Thread.sleep(1) is put in the run() method of a thread超过 2 个线程的工作速度比 1 个或 2 个线程慢,除非将 Thread.sleep(1) 放入线程的 run() 方法中
【发布时间】:2019-09-16 23:32:35
【问题描述】:

我正在尝试实现的任务是使用多个线程在设定的时间间隔内查找 Collat​​z 数字序列,并查看与一个线程相比获得了多少改进。

但是,无论我选择 2 个线程,一个线程总是更快(编辑。2 个线程更快,但不会快很多,而 4 个线程比 1 个线程慢,我不知道为什么。(我什至可以说线程越多越慢)。我希望有人能解释一下。也许我做错了什么。

以下是我目前编写的代码。我正在使用 ThreadPoolExecutor 来执行任务(一个任务 = 一个 Collat​​z 序列,用于间隔中的一个数字)。

Collat​​z 类:

    public class ParallelCollatz implements Runnable {
    private long result;
    private long inputNum;

    public long getResult() {
        return result;
    }
    public void setResult(long result) {
        this.result = result;
    }
    public long getInputNum() {
        return inputNum;
    }
    public void setInputNum(long inputNum) {
        this.inputNum = inputNum;
    }
    public void run() {

        //System.out.println("number:" + inputNum);
        //System.out.println("Thread:" + Thread.currentThread().getId());
        //int j=0;
        //if(Thread.currentThread().getId()==11) {
        //  ++j;
        //  System.out.println(j);
        //}

            long result = 1;

            //main recursive computation
            while (inputNum > 1) {

                if (inputNum % 2 == 0) {
                    inputNum = inputNum / 2;
                } else {
                    inputNum = inputNum * 3 + 1;
                }
                ++result;
            }
           // try {
                //Thread.sleep(10);
            //} catch (InterruptedException e) {
                // TODO Auto-generated catch block
        //      e.printStackTrace();
            //}
            this.result=result;
            return;
        }

}

以及我运行线程的主类(是的,现在我创建了两个具有相同数字的列表,因为在使用一个线程运行后初始值丢失):

        ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
    ThreadPoolExecutor executor2 = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);

    List<ParallelCollatz> tasks = new ArrayList<ParallelCollatz>();
    for(int i=1; i<=1000000; i++) {
        ParallelCollatz task = new ParallelCollatz();
        task.setInputNum((long)(i+1000000));
        tasks.add(task);

    }


    long startTime = System.nanoTime();
    for(int i=0; i<1000000; i++) {
        executor.execute(tasks.get(i));

    }

    executor.shutdown();
    boolean tempFirst=false;
    try {
        tempFirst =executor.awaitTermination(5, TimeUnit.HOURS);
    } catch (InterruptedException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    System.out.println("tempFirst " + tempFirst);
     long endTime = System.nanoTime();
    long    durationInNano = endTime - startTime;
    long    durationInMillis = TimeUnit.NANOSECONDS.toMillis(durationInNano);  //Total execution time in nano seconds
        System.out.println("laikas " +durationInMillis);


        List<ParallelCollatz> tasks2 = new ArrayList<ParallelCollatz>();
        for(int i=1; i<=1000000; i++) {
            ParallelCollatz task = new ParallelCollatz();
            task.setInputNum((long)(i+1000000));
            tasks2.add(task);

        }


        long startTime2 = System.nanoTime();
        for(int i=0; i<1000000; i++) {
            executor2.execute(tasks2.get(i));

        }

        executor2.shutdown();
        boolean temp =false;
        try {
             temp=executor2.awaitTermination(5, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("temp "+ temp);
         long endTime2 = System.nanoTime();
            long durationInNano2 = endTime2 - startTime2;
            long durationInMillis2 = TimeUnit.NANOSECONDS.toMillis(durationInNano2);  //Total execution time in nano seconds
            System.out.println("laikas2 " +durationInMillis2);

例如使用一个线程运行它在 3280 毫秒内完成。用两个线程运行 3437 毫秒。我是否应该考虑另一种并发结构来计算每个元素?

编辑 澄清。我不是在尝试并行化单个序列,而是在每个数字都有其序列时的数字间隔。(与其他数字无关)

EDIT2

今天我在一台具有 6 个内核和 12 个逻辑处理器的良好 PC 上运行该程序,但问题仍然存在。有谁知道问题可能出在哪里?我还更新了我的代码。由于某种原因,4 个线程比 2 个线程做得更差。(甚至比 1 个线程更差)。我也应用了答案中给出的内容,但没有改变。

另一个编辑 我注意到,如果我在我的 ParallelCollat​​z 方法中放置一个 Thread.sleep(1) ,那么性能会随着线程数的增加而逐渐增加。也许这个细节告诉某人出了什么问题?但是,如果没有 Thread.Sleep(1),无论我给出多少任务,2 个线程执行速度最快,1 个线程排在第二位,其他线程挂起的毫秒数相似,但都比 1 个和 2 个线程慢。

新编辑 我还尝试在 Runnable 类的 run() 方法中放置更多任务(用于计算不是 1 而是 10 或 100 个 Collat​​z 序列的循环),以便线程本身可以做更多的工作。不幸的是,这也没有帮助。 也许我错误地启动了任务?有人有什么想法吗?

编辑 因此,在向 run 方法添加更多任务之后,似乎可以稍微修复它,但对于更多线程,问题仍然是 8+。我仍然想知道这是因为创建和运行线程比执行任务需要更多时间吗?还是我应该用这个问题创建一个新帖子?

【问题讨论】:

  • 我正在计算一个序列,比如说 10000 个数字。每个数字都有我试图并行执行的序列,这些序列是不相关的。最终目标是找到最长的,但现在我只是尝试在单独的线程中运行这些序列。
  • 您使用的是 1 个核心吗?
  • 我正在使用一台配备英特尔酷睿 i5-4202Y CPU 的笔记本电脑(不是很好,但它有 2 个内核和 4 个逻辑处理器)。
  • 尝试最大化测试,所以它需要 30 秒而不是 3 秒来完成,并检查它是否使用 2 个逻辑处理器来完成这项工作。如果没有,则没有任何优化,cpu 轮次变化可能会延迟你
  • 这只是理论,似乎是多任务,而不是多线程

标签: java multithreading threadpool executorservice collatz


【解决方案1】:

你不是在等待你的任务完成,只是衡量将它们提交给执行者所花费的时间。

executor.shutdown() 不会等待所有任务完成。之后您需要调用executor.awaitTermination

executor.shutdown();
executor.awaitTermination(5, TimeUnit.HOURS);

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#shutdown()

更新 我相信我们的测试方法是有缺陷的。我在我的机器上重复了你的测试,(1 个处理器,2 个内核,4 个逻辑处理器)并且从运行到运行测量的时间差异很大。

我认为主要原因如下:

  • JVM 启动和 JIT 编译时间。一开始,代码在解释模式下运行。
  • 计算结果被忽略。我不知道 JIT 删除了什么以及我们实际测量的是什么。
  • 代码中的打印行

为了对此进行测试,我将您的测试转换为 JMH。 特别是:

  • 我将 runnable 转换为 callable,并返回结果的总和以防止内联(或者,您可以使用 JMH 的 BlackHole)
  • 我的任务没有状态,我将所有移动部件移动到局部变量。清理任务不需要 GC。
  • 我仍然在每一轮中创建执行器。这并不完美,但我决定保持原样。

下面得到的结果与我的预期一致:一个核心在主线程中等待,工作在一个核心上执行,数量大致相同。

Benchmark                  Mode  Cnt    Score    Error  Units
SpeedTest.multipleThreads  avgt   20  559.996 ± 20.181  ms/op
SpeedTest.singleThread     avgt   20  562.048 ± 16.418  ms/op

更新代码:

public class ParallelCollatz implements Callable<Long> {

    private final long inputNumInit;

    public ParallelCollatz(long inputNumInit) {
        this.inputNumInit = inputNumInit;
    }


    @Override
    public Long call() {
        long result = 1;
        long inputNum = inputNumInit;
        //main recursive computation
        while (inputNum > 1) {

            if (inputNum % 2 == 0) {
                inputNum = inputNum / 2;
            } else {
                inputNum = inputNum * 3 + 1;
            }
            ++result;
        }
        return result;
    }

}

以及基准测试本身:

@State(Scope.Benchmark)
public class SpeedTest {
private static final int NUM_TASKS = 1000000;

    private static List<ParallelCollatz> tasks = buildTasks();

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @SuppressWarnings("unused")
    public long singleThread() throws Exception {
        ThreadPoolExecutor executorOneThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
        return measureTasks(executorOneThread, tasks);
    }

    @Benchmark
    @Fork(value = 1, warmups = 1)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @SuppressWarnings("unused")
    public long multipleThreads() throws Exception {
        ThreadPoolExecutor executorMultipleThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
        return measureTasks(executorMultipleThread, tasks);
    }

    private static long measureTasks(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
        long sum = runTasksInExecutor(executor, tasks);
       return sum;
    }

    private static long runTasksInExecutor(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
        List<Future<Long>> futures = new ArrayList<>(NUM_TASKS);
        for (int i = 0; i < NUM_TASKS; i++) {
            Future<Long> f = executor.submit(tasks.get(i));
            futures.add(f);
        }
        executor.shutdown();

        boolean tempFirst = false;
        try {
            tempFirst = executor.awaitTermination(5, TimeUnit.HOURS);
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        long sum = 0l;
        for (Future<Long> f : futures) {
            sum += f.get();
        }
        //System.out.println(sum);
        return sum;
    }

    private static List<ParallelCollatz> buildTasks() {
        List<ParallelCollatz> tasks = new ArrayList<>();
        for (int i = 1; i <= NUM_TASKS; i++) {
            ParallelCollatz task = new ParallelCollatz((long) (i + NUM_TASKS));

            tasks.add(task);

        }
        return tasks;
    }

}

【讨论】:

  • 我按照你说的做了。现在我得到了 12 秒和 19 秒,但仍然更慢。
  • 您只有 2 个核心。我的猜测是一个被主线程占用,而 2 个工作线程在一个内核上颠簸。我不知道有任何方法可以直接从 Java 中找到当前内核,但是为了获得一些直觉,您可以使用 jconsole/visualvm 连接到您的程序并观察您的线程。他们是否可以同时工作而不会颠簸?
  • 我用一个线程运行执行器,然后打印出时间,然后用 2 个线程启动 executer2 并打印出时间。没有崩溃,据我所知,至少当程序大约 40 秒时,2 个线程的工作速度更快。但是 4 个线程比 1 个线程慢。
  • 对不起,我们不在同一个页面上。 Thrashing(不崩溃)意味着多个线程正在竞争相同的资源(在我的假设中,唯一剩下的核心)并且一次只有一个正在执行,这意味着没有加速,只有额外的上下文切换工作。 jconsole/visualvm 实验是如何工作的?
  • 感谢您提供深入、更新的答案,在尝试了各种方法之后,似乎在线程中计算少量的 Collat​​z 序列并不是可行的方法。当为线程(1000 或 10000)提供更多的序列来计算时,我得到了预期的加速。当然,我也在具有更多内核的机器上运行。再次感谢您的帮助。
猜你喜欢
  • 2013-06-25
  • 2021-05-08
  • 2016-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-22
  • 2021-06-03
  • 2018-07-10
相关资源
最近更新 更多