JGU Logo JGU Logo JGU Logo JGU Logo

Institut für Informatik

Michael Wand
Christian Ali Mehmeti-Göpel
Wintersemester 2020/21DIGITAL

Blatt 10

Aufgabe 1
Einführung in die Softwareentwicklung



Aufgabe Einfache Parallelisierung

Letzte Änderung: 25. January 2021, 12:06 Uhr
14 Punkteim Detail
Ansicht:   |  

Aufgaben

  1. Implementieren Sie die Funktionen
    1. void parallel_transform(std::vector<float> &v, size_t num_threads, std::function<float(float)> f);
      Startet num_threads viele std::threads um die Einträge im Vektor mit f zu transformieren.
      Die Einträge soll gleichmäßig unter den Threads verteilt werden. Der letzte Thread verarbeitet unter Umständen weniger Einträge.

      Statt push_back können Sie Thread-Objekte ohne Kopieren direkt mit emplace_back im Vector-Speicher allokieren. emplace_back erhält dabei dieselben Argumente wie der Konstruktor von std::thread.
    2. void parallel_transform(std::vector<float> &v, size_t num_asyncs, std::function<float(float)> f)
      Nutzt std::asyncs um die selbe Aufgabe zu lösen. C++ kümmert sich hierbei um das Management der Threads. Verteilen Sie hierbei die Einträge wieder gleichmäßig auf die Async-Instanzen.
  2. Messen Sie den Laufzeitunterschied für verschieden große Vektoren und Threadzahlen.

    Hinweis: Direkt im Code kann die Laufzeitmessung etwa mit folgendem Code erreicht werden:
    #include <iostream>
    #include <chrono>
    #include <thread>
    
    int main(){
        using namespace std::chrono_literals;
        std::cout << "Hello waiter\n" << std::flush;
        auto start = std::chrono::high_resolution_clock::now();
        std::this_thread::sleep_for(2s);
        auto end = std::chrono::high_resolution_clock::now();
        std::chrono::duration<double, std::milli> elapsed = end - start;
        std::cout << "Waited " << elapsed.count() << " ms\n";
    }
    
  3. Führen Sie den folgenden Code-Schnipsel aus. Falls „Finished“ bei Ihnen auch nach einigen Sekunden nicht angezeigt wird, erklären Sie, warum das passiert. Welche minimalen Änderungen müssen unternommen werden, damit der Code funktioniert? Erklären Sie diese Wahl kurz.
    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <chrono>
    
    std::mutex mutex1, mutex2;
    
    void thread_a() {
        mutex2.lock();
        std::cout << "Thread A" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        mutex1.lock();
        mutex2.unlock();
        mutex1.unlock();
    }
    
    void thread_b() {
        mutex1.lock();
        std::cout << "Thread B" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        mutex2.lock();
        mutex1.unlock();
        mutex2.unlock();
    }
    
    int main() {
        std::thread t1(thread_a);
        std::thread t2(thread_b);
        t1.join();
        t2.join();
        std::cout << "Finished" << std::endl;
    
        return 0;
    }
    
  4. Wie Sie sicher bemerkt haben, können Sie innerhalb der parallel_transform-Threads keine Ausgabe vornehmen, ohne dass die Zeichen wild durchmischt werden, etwa hier:
    float v_new = f(v);
    std::cout << "alte :" << v << ", neue:" << v_new << std::endl;
    


    1. Erweitern Sie parallel_transform so, dass nach jeder Berechnung eines Wertes durch f das Ergebnis und der Index ausgegeben wird, es aber keine Vermischung der Ausgabe gibt. Verwenden Sie dafür einen Mutex.
    2. Was bedeutet diese Änderung für den Laufzeitgewinn aus Aufgabenteil 1?