Futures: Threads mit Rückgabewert

Veröffentlicht am: Sunday, May 17, 2020
Lesezeit: 2 Minuten Lesezeit
Tags: c++, tips

Die Arbeit mit Threads ist seit C++11 deutlich einfacher geworden.

Folgendes Beispiel zeigt wie std::thread verwendet werden kann:

#include <iostream>
#include <vector>
#include <math.h>
#include <thread>

std::vector<int> calcPrimes(int maxNumber) {
    std::vector<int> primes;
    for (int c = 2; c <= maxNumber; ++c) {
        bool prime = [&c]()->bool{
            for (int i = 2; i <= sqrt(c); ++i) {
                if (c % i == 0) { return false; }
            }
            return true;
        }();
        if (prime) {
            primes.push_back(c);
        }
    }
    return primes;
}

int main() {
    int maxNumber = 1000000;
    std::vector<int> primes;
    auto t = std::thread([&primes](int n)->void {
        auto calculatedPrimes = calcPrimes(n);
        primes.insert(primes.end(), calculatedPrimes.begin(), calculatedPrimes.end());
    }, maxNumber);
    
	// Do other stuff
    
    t.join();
    std::cout << "Generated " << primes.size() << " primes." << std::endl;
}

Aber dieses Beispiel ist nicht Threadsicher.

Um es Threadsicher zu machen müssen wir den Vector primes vor konkurrierendem Zugriff schützen.

Ein Beispiel dazu sähe folgendermaßen aus:

#include <iostream>
#include <vector>
#include <math.h>
#include <thread>
#include <mutex>

class ThreadsafePrimeCalculator
{
public:
    void calcPrimes(int maxNumber) {
        if (t.joinable()) t.join();
        t = std::thread([this](int maxNumber) -> void { _calcPrimes(maxNumber); }, maxNumber);
    }

    std::vector<int> getPrimes() {
        if (t.joinable()) t.join();
        std::lock_guard guard{m};
        std::vector<int> copy = primes;
        return std::move(copy);
    }
    ~ThreadsafePrimeCalculator() {
        if (t.joinable()) t.join();
    }
private:
    void _calcPrimes(int maxNumber) {
        std::lock_guard guard{m};
        primes.clear();
        for (int c = 2; c <= maxNumber; ++c) {
            bool prime = [&c]()->bool{
                for (int i = 2; i <= sqrt(c); ++i) {
                    if (c % i == 0) { return false; }
                }
                return true;
            }();
            if (prime) {
                primes.push_back(c);
            }
        }
    }
    std::thread t;
    std::vector<int> primes;
    std::mutex m;
};

int main() {
    int maxNumber = 1000000;
    ThreadsafePrimeCalculator tspc{};
    tspc.calcPrimes(maxNumber);

	// Do other stuff

    std::cout << "Generated " << tspc.getPrimes().size() << " primes." << std::endl;
}

Aber um uns das zu sparen bietet C++ ein anderes Feature: std::future

Futures sind Objekte die in der Zukunft ein Ergebnis zurückliefern. Im Grunde sind es Threads mit Rückgabewert.

Das obere Beispiel lässt sich damit Threadsicher umschreiben ohne Lock Guards und Mutex.

#include <iostream>
#include <vector>
#include <math.h>
#include <thread>
#include <future>

std::vector<int> calcPrimes(int maxNumber) {
    std::vector<int> primes;
    for (int c = 2; c <= maxNumber; ++c) {
        bool prime = [&c]()->bool{
            for (int i = 2; i <= sqrt(c); ++i) {
                if (c % i == 0) { return false; }
            }
            return true;
        }();
        if (prime) {
            primes.push_back(c);
        }
    }
    return primes;
}

int main() {
    int maxNumber = 1000000;
    auto a = std::async(std::launch::async, calcPrimes, maxNumber);

    // Do other stuff

    std::cout << "Generated " << a.get().size() << " primes." << std::endl;
}