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