сравнение производительности кода, многопоточного и непоточного - PullRequest
2 голосов
/ 30 октября 2008

У меня есть несколько вопросов, связанных с потоками, при условии, что следующий код. Пожалуйста, игнорируйте возможную неэффективность кода, меня интересует только часть потока.

//code without thread use
public static int getNextPrime(int from) {
    int nextPrime = from+1;
    boolean superPrime = false;
    while(!superPrime) {
        boolean prime = true;
        for(int i = 2;i &lt nextPrime;i++) {
            if(nextPrime % i == 0) {
                prime = false;
            }
        }
        if(prime) {
            superPrime = true;
        } else {
            nextPrime++;
        }
    }
    return nextPrime;
}

public static void main(String[] args) {
   int primeStart = 5;
   ArrayList list = new ArrayList();
   for(int i = 0;i &lt 10000;i++) {
       list.add(primeStart);
       primeStart = getNextPrime(primeStart);
   }
}

Если я запускаю такой код, и это занимает около 56 секунд. Если, однако, у меня есть следующий код (в качестве альтернативы):

public class PrimeRunnable implements Runnable {

    private int from;
    private int lastPrime;

    public PrimeRunnable(int from) {
        this.from = from;
    }

    public boolean isPrime(int number) {
        for(int i = 2;i &lt from;i++) {
            if((number % i) == 0) {
                return false;
            }
        }
        lastPrime = number;
        return true;
    }

    public int getLastPrime() {
        return lastPrime;
    }

    public void run() {
        while(!isPrime(++from))
            ;
    }
}

public static void main(String[] args) {
   int primeStart = 5;
   ArrayList list = new ArrayList();
   for(int i = 0;i &lt 10000;i++) {
     PrimeRunnable pr = new PrimeRunnable(primeStart);
     Thread t = new Thread(pr);
     t.start();
     t.join();
     primeStart = pr.getLastPrime();
     list.add(primeStart);
   }
}

Вся операция занимает около 7 секунд. Я почти уверен, что, хотя я создаю только один поток за раз, поток не всегда заканчивается, когда создается другой. Это правильно? Мне также любопытно: почему операция заканчивается так быстро?

Когда я присоединяюсь к потоку, другие потоки продолжают работать в фоновом режиме, или присоединенный поток является единственным запущенным?

Ответы [ 5 ]

3 голосов
/ 30 октября 2008

Поместив join () в цикл, вы запускаете поток, затем ждете остановки этого потока перед запуском следующего. Я думаю, что вы, вероятно, хотите что-то вроде этого:

public static void main(String[] args) {
   int primeStart = 5;

   // Make thread-safe list for adding results to
   List list = Collections.synchronizedList(new ArrayList());

   // Pull thread pool count out into a value so you can easily change it
   int threadCount = 10000;
   Thread[] threads = new Thread[threadCount];

   // Start all threads
   for(int i = 0;i < threadCount;i++) {
     // Pass list to each Runnable here
     // Also, I added +i here as I think the intention is 
     //    to test 10000 possible numbers>5 for primeness - 
     //    was testing 5 in all loops
     PrimeRunnable pr = new PrimeRunnable(primeStart+i, list);
     Thread[i] threads = new Thread(pr);
     threads[i].start();  // thread is now running in parallel
   }

   // All threads now running in parallel

   // Then wait for all threads to complete
   for(int i=0; i<threadCount; i++) {
     threads[i].join();
   }
}

Кстати, pr.getLastPrime () вернет 0 в случае отсутствия простого числа, так что вы можете отфильтровать это перед добавлением в свой список. PrimeRunnable должен поглощать работу по добавлению в список окончательных результатов. Кроме того, я думаю, что PrimeRunnable был на самом деле сломан из-за увеличения кода. Я думаю, что это исправлено, но на самом деле я не собираю это.

public class PrimeRunnable implements Runnable {    
    private int from;
    private List results;   // shared but thread-safe

    public PrimeRunnable(int from, List results) {
        this.from = from;
        this.results = results;
    }

    public void isPrime(int number) {
        for(int i = 2;i < from;i++) {
                if((number % i) == 0) {
                        return;
                }
        }
        // found prime, add to shared results
        this.results.add(number);
    }

    public void run() {
        isPrime(from);      // don't increment, just check one number
    }    
}

Запуск 10000 потоков параллельно - не очень хорошая идея. Намного лучше создать пул с фиксированными потоками разумного размера и заставить их извлекать работу из общей очереди. По сути, каждый работник извлекает задачи из одной и той же очереди, работает над ними и сохраняет результаты где-то. Ближайший порт этого в Java 5+ - это использование ExecutorService, поддерживаемого пулом потоков. Вы также можете использовать CompletionService, который объединяет ExecutorService с очередью результатов.

Версия ExecutorService будет выглядеть так:

public static void main(String[] args) {
   int primeStart = 5;

   // Make thread-safe list for adding results to
   List list = Collections.synchronizedList(new ArrayList());

   int threadCount = 16;  // Experiment with this to find best on your machine
   ExecutorService exec = Executors.newFixedThreadPool(threadCount);

   int workCount = 10000;  // See how # of work is now separate from # of threads?
   for(int i = 0;i < workCount;i++) {
     // submit work to the svc for execution across the thread pool 
     exec.execute(new PrimeRunnable(primeStart+i, list));
   }

   // Wait for all tasks to be done or timeout to go off
   exec.awaitTermination(1, TimeUnit.DAYS);
}

Надеюсь, это дало вам некоторые идеи. И я надеюсь, что последний пример казался намного лучше, чем первый.

2 голосов
/ 30 октября 2008

JesperE прав, но я не верю только в намеки (по крайней мере, вне класса):

Обратите внимание на этот цикл в версии без резьбы:

for(int i = 2;i < nextPrime;i++) {
  if(nextPrime % i == 0) {
    prime = false;
  }
}

В отличие от этого в версии с резьбой:

for(int i = 2;i < from;i++) {
  if((number % i) == 0) {
    return false;
  }
}

Первый цикл всегда будет проходить полностью, а второй выйдет рано, если найдет делитель.

Вы могли бы также заставить первый цикл завершить работу раньше, добавив оператор break следующим образом:

for(int i = 2;i < nextPrime;i++) {
  if(nextPrime % i == 0) {
    prime = false;
    break;
  }
}
2 голосов
/ 30 октября 2008

Вы можете проверить это лучше, сделав точный код в первом примере с потоками. Sub ваш основной метод с этим:

    private static int currentPrime;
public static void main(String[] args) throws InterruptedException {
    for (currentPrime = 0; currentPrime < 10000; currentPrime++) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                getNextPrime(currentPrime);
            }});
        t.run();
        t.join();
    }
}

Это будет работать в то же время, что и оригинал.

Чтобы ответить на ваш вопрос о присоединении: да, другие потоки могут работать в фоновом режиме, когда вы используете "объединение", но в этом конкретном случае у вас будет только один активный поток за раз, потому что вы блокируете создание новых потоков, пока не завершится выполнение последнего потока.

1 голос
/ 30 октября 2008

Внимательно прочитайте ваш код. Два случая не делают одно и то же, и это не имеет ничего общего с потоками.

Когда вы присоединяетесь к потоку, другие потоки будут работать в фоновом режиме, да.

0 голосов
/ 30 октября 2008

Запуск теста, второй, кажется, не занимает 9 секунд - на самом деле, он занимает по крайней мере столько же времени, сколько первый (что и следовало ожидать, threding не может помочь, как он реализован в пример.

Thread.join вернется только после завершения thread.joined, затем текущий поток продолжится, тот, на котором вы вызвали join, будет мертв.

Для краткости: подумайте, что многопоточность при запуске одной итерации не зависит от результата предыдущей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...