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