2008-10-29 4 views
2

J'ai quelques questions liées au thread, en supposant le code suivant. S'il vous plaît ignorer l'inefficacité possible du code, je suis seulement intéressé par la partie thread.comparaison de la performance du code, threaded contre non-threaded

//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 < 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 < 10000;i++) { 
     list.add(primeStart); 
     primeStart = getNextPrime(primeStart); 
    } 
} 

Si je cours le code comme ceci et cela prend environ 56 secondes. Si, cependant, j'ai le code suivant (comme une alternative):

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 < 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 < 10000;i++) { 
    PrimeRunnable pr = new PrimeRunnable(primeStart); 
    Thread t = new Thread(pr); 
    t.start(); 
    t.join(); 
    primeStart = pr.getLastPrime(); 
    list.add(primeStart); 
    } 
} 

L'opération entière prend environ 7 secondes. Je suis presque certain que même si je ne crée qu'un seul fil à la fois, un fil ne finit pas toujours quand un autre est créé. Est-ce correct? Je suis aussi curieux: pourquoi l'opération se termine-t-elle si vite?

Lorsque je rejoins un thread, d'autres threads continuent-ils à s'exécuter en arrière-plan ou le thread connecté est-il le seul en cours d'exécution?

Répondre

3

En plaçant la jointure() dans la boucle, vous commencez un fil, puis d'attendre que le fil s'arrêter avant d'exécuter le suivant. Je pense que vous voulez probablement quelque chose comme ceci:

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(); 
    } 
} 

Par ailleurs pr.getLastPrime() retourne 0 dans le cas d'aucun premier, vous voudrez peut-être filtre avant de l'ajouter à votre liste. Le PrimeRunnable doit absorber le travail d'ajout à la liste des résultats finaux. En outre, je pense que PrimeRunnable a été cassé en ayant toujours du code incrémenté dedans. Je pense que c'est corrigé, mais je ne compile pas cela.

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 
    }  
} 

Exécuter 10000 threads en parallèle n'est pas une bonne idée. C'est une bien meilleure idée de créer un pool de threads fixes de taille raisonnable et de les faire travailler à partir d'une file d'attente partagée. Fondamentalement, chaque travailleur tire des tâches de la même file d'attente, travaille sur eux et enregistre les résultats quelque part. Le port le plus proche de Java 5+ est d'utiliser un ExecutorService soutenu par un pool de threads. Vous pouvez également utiliser un CompletionService qui combine un ExecutorService avec une file d'attente de résultats.

Une version ExecutorService ressemblerait à ceci:

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

espoir que vous a donné quelques idées. Et j'espère que le dernier exemple a semblé beaucoup mieux que le premier.

+0

Bon exemple! Je vous remercie ! – Geo

+0

+1: Génial, très utile. – sixtyfootersdude

1

Lisez attentivement votre code. Les deux cas ne font pas la même chose, et cela n'a rien à voir avec les threads. Lorsque vous joignez un thread, d'autres threads s'exécuteront en arrière-plan, oui.

+0

Pourriez-vous me dire ce que vous entendez par "Les deux cas ne font pas la même chose"? – Geo

+0

La raison pour laquelle l'un des cas est beaucoup plus lent est qu'il ne fait pas la même chose. Astuce: combien de fois appelez-vous respectivement isPrime() et getNextPrime()? – JesperE

0

En exécution d'un test, le second ne semble pas prendre 9 secondes - en fait, il prend au moins aussi longtemps que le premier (ce qui est prévisible, threding ne peut pas aider la façon dont il est mis en œuvre votre exemple

Thread.join ne reviendra quand les thread.joined se termine, le thread courant continuera, celui que vous avez appelé à rejoindre sera mort

Pour une référence rapide -.. think filetage quand commencer une itération ne dépend pas du résultat du précédent

+0

Je les ai essayés tous les deux sur mon ordinateur portable. Les temps que j'ai écrits dans le poteau sont les fois que je reçois. – Geo

+0

Peut-être que quelque chose sur la façon dont nos machines virtuelles traitent les choses différemment. Peut-être que votre thread.join est cassé, cela expliquerait la différence d'exécution ... En outre, le second a jeté une exception que vous ne manipuliez pas. Je ne suis pas sûr de la façon dont vous l'avez exécuté. Quelle VM utilisez-vous? –

2

Vous pouvez tester cela mieux en faisant le code exact dans votre premier exemple exécuté avec des discussions. Sous votre méthode principale avec ceci:

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(); 
    } 
} 

Ceci fonctionnera dans le même temps que l'original. Pour répondre à votre question de "jointure": oui, d'autres threads peuvent être exécutés en arrière-plan lorsque vous utilisez "join", mais dans ce cas particulier vous n'aurez qu'un seul thread actif à la fois, car vous bloquez le création de nouveaux threads jusqu'à l'exécution du dernier thread.

+0

Que se passerait-il si j'ajoutais le thread créé à une liste, puis que je le rejoignais après avoir quitté la boucle? Et si je fais cela, aurai-je 10000 threads en cours d'exécution simultanément? – Geo

+0

Eh bien, l'échantillon que j'ai écrit a un int statique qui est manipulé à l'intérieur de la boucle, donc vous devrez faire quelques modifications pour que cela fonctionne. Toutefois, si vous avez sécurisé le thread de code, vous pouvez mettre ces threads en file d'attente et les exécuter simultanément si vous le souhaitez. –

+0

Cependant, avec un grand nombre de threads, vous devriez regarder java.util.concurrent.ThreadPoolExecutor au lieu d'essayer de les exécuter tous en un seul coup. –

2

JesperE est juste, mais je ne crois pas seulement à donner des conseils (au moins en dehors d'une salle de classe):

Notez cette boucle dans la version non-thread:

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

Contrairement à ceci dans la version filetée:

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

la première boucle toujours fonctionner complètement à travers, tandis que le second quittera plus tôt si elle trouve un diviseur.

Vous pouvez faire la première boucle sortir aussi tôt en ajoutant une déclaration de rupture comme ceci:

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