17 May 2020

Futures: Threads mit Rückgabewert

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

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

#include 
#include 
#include 
#include 
std::vector calcPrimes(int maxNumber) {
    std::vector 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 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 
#include 
#include 
#include 
#include 
class ThreadsafePrimeCalculator
{
public:
    void calcPrimes(int maxNumber) {
        if (t.joinable()) t.join();
        t = std::thread([this](int maxNumber) -> void { _calcPrimes(maxNumber); }, maxNumber);
    }
    std::vector getPrimes() {
        if (t.joinable()) t.join();
        std::lock_guard guard{m};
        std::vector 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 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 
#include 
#include 
#include 
#include 
std::vector calcPrimes(int maxNumber) {
    std::vector 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;
}

Blogroll

Online Services

Neocities Eine moderne Version von Geocities. Manches Gem zu finden.
Gemini Protokolldefinition Eine moderne Version von Gopher
Posteo Ein sehr guter und sympathsicher Berliner Email Service.

Retro Computing

CBBS Outpost Aktuelle Liste von C64/C128 BBS Servern