int rez = f1() + f2(); // sekvencijalno izvršavanje
Kada dio programa želimo izvršiti paralelno u posebnoj dretvi zatvorimo ga
u funkciju (funkcijski objekt ili lambdu) i funkciju izvršimo kroz std::async().
Funkcija std::async() je definirana u zaglavlju <future> i uzima objekt koji se može
pozvati kao funkcija f te argumente koje f zahtjeva. Ako je moguće funkcijski objekt f će biti
izvršen u posebnoj dretvi, paralelno s glavnom dretvom programa.
Ako f vraća neku vrijednost std::async() će tu vrijednost vratiti kroz std::future<> objekt.
Primjer. Pretpostavimo da imamo dvije funkcije f1() i f2() koje vraćaju rezultat tipa int
i da nas zanima suma povratnih vrijednosti tih funkcija:
int rez = f1() + f2(); // sekvencijalno izvršavanje
Paralelna verzija koda je dana ovdje:
future<int> fut{async(f1)}; // Izvršava se u posebnoj dretvi (eventualno).
int i2 = f2();
int i1 = fut.get(); // Čekaj da posebna dretva završi i preuzmi rezultat
rez = i1 + i2;
std::async() i std::future<>Ako imamo funkciju koja vraća reziltat tipa T
T f();
koju želimo izvršiti u zasebnoj dretvi, paralelno s glavnom dretvom, onda ju predajemo funkcji
std::async() (zaglavlje <future>):
auto fut = std::async(f);
funkcija std::async() pokreće funkciju f() u (eventualno) novoj dretvi i odmah vraća
objekt tipa std::future<T> (zaglavlje <future>).
fut (tipa std::future<T>) predstavlja budući rezultat izvršavanja funcije f().
Taj rezultat dobivamo pozivanjem metode get() na fut objektu. Poziv T rez = fut.get()
(u glavnoj dretvi) blokira glavnu dretvu sve dok rezultat nije spreman. Tada get() vraća
povratnu vrijednost funkcije f().
f() stavljamo između poziva
auto fut = std::async(f); i poziva T rez = fut.get().
Ako funkcija f() ne vraća ništa (T = void) onda je povratna vrijednost od std::async()
tipa std::future<void> i metoda std::future<void>::get() ne vraća ništa već samo čeka da f() završi.
std::async()Moguća je situacija u kojoj std::async() u naredbi auto fut = std::async(f);
ne može kreirati novu dretvu. Tada će poziv funkcije f() biti odgođen do poziva
T rez = fut.get(). Program se u tom slučaju odvija serijski.
Moguće je eksplicitno odrediti ponašanje funkcije std::async() dodavanjem novog parametra:
auto fut = std::async(std::launch::async, f); Pokreni funkciju f() u posebnoj dretvi.
U slučaju da se dretva ne može kreirati izbacuje izuzetak std::system_error.
auto fut = std::async(std::launch::deferred, f); Pokreni funkciju f() serijski
(u glavnoj dretvi) kada bude zatražen rezultat, dakle u pozivu T rez = fut.get().
auto fut = std::async(f); tada std::async() pokušava kreirati
novu dretvu i ako ne uspije prelazi na ponašanje određeno sa std::launch::deferred.
Preporuka je ne koristiti eksplicitno std::launch::async i prepustiti sustavu da odredi može li
se kreirati nova dretva ili će se kod izvršiti serijski.
Funkciju std::future<>::get() smijemo pozvati samo jednom. Nakon tog poziva
std::future<> objekt nije u ispravnom stanju i sljedeći poziv get() metode daje
grešku pri izvršavanju. Stoga std::future<> ima metodu valid() koja vraća true
ako matoda get() još nije pozvana.
if(fut.valid()) // Provjerava je li future u ispravnom stanju
fut.get();
Kod koji izvršavamo paralelno može izbaciti izuzetak. U tom će slučaju metoda std::future<void>::get()
izbaciti (proslijediti) taj izuzetak.
double * f(){
double *p = nullptr;
long limit = std::numeric_limits<long>::max();
p = new double[limit]; // Izbacuje izuzetak
return p;
}
int main(){
auto fut{async(f)};
// ... paralelan kod
try{
double * p = fut.get();
}
catch(const exception & e){
cerr << "Exception: " << e.what() << endl;
}
return 0;
}
std::future<>::wait()Moguće je čekati da funkcija pokrenuta s std::async() završi bez procesiranja rezultata.
To omogućava funkcija std::future<>::wait() koja se za razliku od std::future<>::get()
može pozvati više puta. Funkcija std::future<>::wait() će pokrenuti izvršavanje funkcije ako je
bilo odgođeno. Da bismo dobili rezultat izvršavanja moramo zvati std::future<>::get().
std::future<>::wait_for() i std::future<>::wait_until()Ove dvije funkcije čekaju da dretva završi određeni vremenski interval ili do određenog trenutka. One neće startati dretvu ako je odgođena. Vraćaju jednu od tri vrijednosti:
std::future_status::deferred ako je async() nije startao funkciju;
std::future_status::timeout ako je funkcija startana u dretvi ali nije gotova;
std::future_status::ready ako je funkcija završila.
Funkcija std::async() nakon funkcijskog objekta (funkcije ili lambde) uzima proizvoljan broj
parametara koje predaje funkcijskom objektu koji izvršava. Broj argumenata je proizvoljan i svi se
prenose po vrijednosti.
double * f(size_t n, double a){
double *p = nullptr;
p = new double[n];
fill(p, p+n, init);
// ....
return p;
}
int main(){
auto fut = async(f, 100000, 3.14);
// ... paralelan kod
double * p = fut.get();
// ...
delete [] p;
return 0;
}
Za prijenos po referenci moramo koristiti std::ref (zaglavlje <functional>).
double * f(size_t n, double & a){
// ....
return p;
}
int main(){
double a = 17.0;
auto fut = async(f, 100000, std::ref(a));
// ... paralelan kod
double * p = fut.get();
// ...
delete [] p;
return 0;
}
std::this_threadDaje sljedeće funkcije (zaglavlje <thread>):
this_thread::get_id() daje ID trenutne dretve
this_thread::sleep_for(dur) blokira dretvu za trajanja dur
this_thread::sleep_until(tp) blokira dretvu sve do trenutka tp
this_thread::yield() sugerira prepuštanje procesora sljedećoj dretvi
std::threadKlasa std::thread pripada sučelju niske razine. Slična je donekle funkciji std::async().
Klasa std::thread:
std::async(), konstruktor klase std::thread uzima funkcijski objekt i argumente koje mu treba predati
te odmah starta funkcijski objekt u posebnoj dretvi (ili izbacuje std::system_error);
std::async(), konstruktor klase std::thread može uzeti funkciju, funkcijski objekt i lambdu;
std::ref();
std::thread objektu dobiva pomoću get_id() metode;
join() ako želi čekati da dretva završi, ili detach() ako želi da dretva nastavi
raditi u pozadini.
void doSomething();
std::thread t(doSomething); // starta doSomething() u pozadini
// ...
t.join(); // čekaj da doSomething() završi
std::promise<>Objekt tipa std::promise<T> služi za komunikaciju vrijednosti tipa T iz jedne dretve u drugu.
Umjesto vrijednosti može držati izuzetak ako je izračunavanje vrijednosti završilo izbacivanjem izuzetka.
Metode:
set_value(t) - postavi vrijednost na t;
set_exception(e) - spremi izuzetak e (tip exception_ptr). Izuzetak koji je izbačen može se dobiti sa
current_exception() (zaglavlje <stdexcept>).
get_future() - vrati future<T> objekt vezan uz dijeljeno stanje.
Kada dretva stavi vrijednost u std::promise<T> objekt (podatak ili izuzetak) pripadni future<T> objekt
postaje spreman i dretva koja čeka na njemu dobiva vrijednost (ili izuzetak) i nastavlja izvršavanje.