Passaggio da multithreading C ++ a multithreading Java

4

In C ++, sono stato abituato a utilizzare i thread nel modo seguente :

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;
int i = 0; 
void makeACallFromPhoneBooth() 
{
    m.lock();
      std::cout << i << " Hello Wife" << std::endl;
      i++;
    m.unlock();//man unlocks the phone booth door 
}

int main() 
{
    std::thread man1(makeACallFromPhoneBooth);
    std::thread man2(makeACallFromPhoneBooth);

    man1.join();
    man2.join();
    return 0;
}

Ma quando ho visto i tutorial su multithreading Java , l'intera classe sembra gestire l'avvio e l'esecuzione del thread Uniti.

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;

   RunnableDemo( String name){
       threadName = name;
       System.out.println("Creating " +  threadName );
   }
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(50);
         }
     } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
     }
     System.out.println("Thread " +  threadName + " exiting.");
   }

   public void start ()
   {
      System.out.println("Starting " +  threadName );
      if (t == null)
      {
         t = new Thread (this, threadName);
         t.start ();
      }
   }

}

public class TestThread {
   public static void main(String args[]) {

      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
   }   
}

La domanda:

  • Bisogna cambiare il modo in cui si progettano le classi solo per essere in grado fare uso del threading?
  • Se ho già un programma creato senza threading e lo voglio introdurre il threading, avrei potuto farlo facilmente in C ++. Ma qui, esso sembra che dovrò ridisegnare l'intera classe o renderla ereditaria da Runnable . È davvero così che lavori con i thread Java o lo sono dovevi progettarlo per il threading fin dall'inizio invece di introdurlo più tardi?

Un esempio di una classe che sto usando è questo:

public class abc extends abcParent {
//many member variables and functions here

public calculateThreshold() {
    for(int i = 0; i<zillion; ++i) {
        doSomethingThatIsParallelizable();
    }
}//calculateThreshold

private doSomethingThatIsParallelizable() {
    while(something) {
        //do something else which is parallelizable
    }//while
}//doSomething

}//class

L'utilizzo degli stream non è apparentemente consigliabile , quindi, come si fa a progettare una classe per il threading? Non sembra logico che abc abbia le funzioni start e run . È sciocco. abc riguarda solo la funzionalità di abc . Chiamerei abcInstance.calculateThreshold(); e avrebbe senso, ma chiamare abcInstance.run() anziché calculateThreshold() non ha la stessa leggibilità. Cosa consiglieresti?

    
posta Nav 14.03.2016 - 06:55
fonte

3 risposte

4

Queste due API sono quasi identiche. La differenza è la mancanza di modelli e sovraccarico dell'operatore e il fatto che è necessario chiamare start () sugli oggetti Thread di Java.

Il costruttore C ++ std :: thread è un modello che accetta una funzione. L'interfaccia per la funzione è "()" - che può essere una semplice vecchia funzione, o un oggetto (come un lambda o un'altra funzione std ::) che ha sovraccaricato l'operatore ().

Thread di Java prende un oggetto che ha sottoclassi Runnable e implementato Run (). È lo stesso schema, tranne che non ci sono template, il che significa che deve esserci una classe base e nessun overloading dell'operatore, quindi il metodo che implementate è denominato Run () e non operator ().

Hai ragione, però, che in Java (e in C ++!) in genere vuoi separare la logica per gestire il threading dal calcolo reale. Un approccio comune è quello di avere un threadpool. Ciò ti consente di limitare il numero di thread paralleli in esecuzione: non ha senso eseguire un numero di zillion di thread separati e ti impedisce di dover pagare i costi di avvio / smantellamento di un singolo thread per un miliardo di volte.

A differenza di C ++ (finora) la libreria standard di Java ha già un'implementazione del pool di thread. java.lang.concurrent.ThreadPoolExecutor funzionerebbe abbastanza bene qui - calculateThreshold () accoda gli elementi nella sua coda, dove ogni elemento è un Runnable che chiama semplicemente doSomethingThatIsParallelizable ().

    
risposta data 14.03.2016 - 08:23
fonte
2

(Questo è un lungo commento convertito in una risposta. Il suo scopo è menzionare il nome di diversi paradigmi, che permetteranno di trovare più materiale di lettura.)

Esistono diversi paradigmi leggermente diversi:
(questo elenco non è esaustivo)

  1. Thread esplicito, in cui un thread padre chiede esplicitamente a un thread figlio di essere creato, eseguito e distrutto dopo l'uso
  2. Parallelismo delle attività, in cui le linee di codice da eseguire vengono impacchettate in un'attività, che verrà sottoposta all'esecutore.
    • L'attività è un oggetto, poiché deve avere una durata specifica per la durata dell'attività.
  3. Parallelismo fork-join , che consente la creazione di sottoprocessi da un'attività padre già in esecuzione . È una miscela dei primi due.

All'inizio di Java, l'unico esecutore è un nuovo thread - quindi è fondamentalmente lo stesso meccanismo del thread esplicito. ExecutorService è stato introdotto in Java 1.5 (che era più più di dieci anni fa), insieme a diverse implementazioni, una delle quali si basa su un pool di thread.

Per quanto riguarda la progettazione del software, è necessario studiare attentamente i paradigmi e decidere quale si adatta meglio all'applicazione.
(Gli esempi seguenti sono solo esempi introduttivi. Le applicazioni reali hanno molte più considerazioni e vincoli.)

  • Per un'architettura server-client che utilizza un modello a thread singolo per connessione, è necessario utilizzare il threading esplicito, poiché ogni thread può durare indefinitamente finché la connessione è aperta. Si noti che la maggior parte dei thread dormirà la maggior parte del tempo. Il numero massimo di thread non dipende dal numero di core della CPU.
  • Per un'applicazione computazionalmente intensiva che suddivide una quantità fissa di attività di calcolo (il cui numero è noto all'inizio dell'attività) su più core CPU per ottenere l'accelerazione, un pool di thread con un numero fisso di thread uguale a il numero di core della CPU sarebbe più adatto.
  • Per un'applicazione computazionalmente intensiva che suddivide un'attività di calcolo definita in modo ricorsivo la cui quantità totale non è ancora nota o è variabile, è necessario il parallelismo fork-join.

Una volta identificato il miglior paradigma per la tua applicazione, si consiglia di ridefinire la tua applicazione verso quel paradigma. Questo suggerimento si applica a C ++ e Java (e ad alcuni altri linguaggi di programmazione generici) .

Per C ++, è necessario trovare un'implementazione di parallelismo delle attività di terze parti. Intel Task Building Blocks (TBB) e Microsoft Parallel Patterns Library (PPL) sono due implementazioni di questo tipo.

È possibile scrivere la propria implementazione del parallelismo delle attività in C ++. Per fare ciò, è necessario implementare una grande quantità di strutture di dati concorrenti (soprattutto le code concorrenti) e alcuni requisiti imposti rispetto all'uso delle eccezioni C ++.

    
risposta data 24.03.2016 - 22:15
fonte
0

Sì, Java è più dettagliato di C ++, ma non è quello dettagliato. Ecco come traduco il tuo codice C ++ in Java8:

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;

class MyDemo {
    static final Lock m = new ReentrantLock();
    static int i = 0;
    static void makeACallFromPhoneBooth() 
    {
        m.lock();
        try {
          System.out.format("%d Hello Wife\n", i);
          i++;
        } finally {
          m.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        Thread man1 = new Thread(() -> makeACallFromPhoneBooth());
        Thread man2 = new Thread(() -> makeACallFromPhoneBooth());

        man1.start();
        man2.start();

        man1.join();
        man2.join();
    }
}

La sintassi, () -> makeACallFromPhoneBooth() è ciò che passa per un'espressione lambda in Java8. Il suo valore non è esattamente una funzione (come espressioni lambda in altri linguaggi) ma è abbastanza vicino: crea una nuova istanza di una classe anonima che implementa l'interfaccia Runnable .

Il compilatore sa che deve essere un Runnable per tipo di inferenza: questo è ciò che il costruttore di Thread(r) si aspetta. E, il compilatore sa di rendere il corpo della lambda nel metodo run() della classe anonima, perché questo è l'unico metodo che Runnable dichiara.

    
risposta data 24.03.2016 - 21:21
fonte

Leggi altre domande sui tag