- https://en.wikipedia.org/wiki/Futures_and_promises
- http://en.cppreference.com/w/cpp/thread/future
- http://en.cppreference.com/w/cpp/thread/async
- http://en.cppreference.com/w/cpp/thread/promise
- http://en.cppreference.com/w/cpp/thread/packaged_task
- http://stackoverflow.com/questions/11004273/what-is-stdpromise
Do asynchronicznego wołania funkcji służy std::async. Jest to rozwiązanie najbardziej wysokopoziomowe, gdyż sam std::async dba o to by ustawić std::future w stan ready. Wywołanie oprócz ewentualnych argumentów jakie mają być przekazane do funkcji wymaga również podania policy:
- std::launch::async - nowy wątek zostanie uruchomiony do wykonania zadania
- std::launch::deferred - wykonanie zadania zostaje odroczone do momentu wywołania metod get() lub wait()
- std::launch::async | std::launch::deferred - kombinacja flag, jednak zachowanie jest zależne od implementacji
$ clang++ -std=c++14 -fsanitize=thread -lpthread -g main.cppPrzykład obliczania ciągu Fibonacciego z zastosowaniem std::async.
#include <iostream>
#include <future>
using namespace std;
int fibonacci(int n)
{
std::cout << "Current n: " << n << std::endl;
if (n == 0)
return 0;
else if (n == 1)
return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main()
{
std::future<int> result = std::async(std::launch::async | std::launch::deferred,
fibonacci, 3);
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
Wynik:Current n: 3 Current n: 2 Current n: 1 Current n: 0 Current n: 1 Result: 2std::packaged_task także pozawala na tworzenie obiektów std::future. Można go porównać do std::function, a więc jako wrapper do tworzenia obiektów callable. W przeciwieństwie do std::async, nie uruchamia on jednak przekazanej funkcji automatycznie. Najważniejszą właściwością std::packaged_task jest możliwość pozyskania z niego obiektu std::future, i przekazania go do innego wątku, gdzie zostanie wykonany.
Przykład:
#include <iostream>
#include <thread>
#include <future>
using namespace std;
int fibonacci(int n)
{
std::cout << "Current n: " << n << std::endl;
if (n == 0)
return 0;
else if (n == 1)
return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main()
{
std::packaged_task<int(int)> task{fibonacci};
std::future<int> result = task.get_future();
std::thread t{std::move(task), 3};
t.join();
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
Wynik:Current n: 3 Current n: 2 Current n: 1 Current n: 0 Current n: 1 Result: 2std::promise można rozważyć jako początek kanału komunikacyjnego, na którego końcu znajduje się std::future. Do jego zadań, należy zapisanie wyniku do współdzielonego stanu. Czasami dane, które nas interesują są już dostępne (obliczone) i nie musimy czekać na zakończenie jakiegoś wątku. Za pomocą std::promise możemy w wątku głównym wytworzyć obiekt std::future (czyli końcówka kanału - dzięki metodzie get_future()), a następnie przekazać go (std::promise) do innego wątku. W wątku pobocznym, gdy interesujące nas dane zostaną obliczone wołamy metodę set_value() (początek kanału). W ten sposób obiekt std::future zostanie ustawiony w stan ready, i dane będą mogły być odczytane w wątku głównym. W porównywaniu do std::async, jest to rozwiązaniem bardziej nisko poziomowe, lecz czasami zachodzi potrzeba aby z niego skorzystać.
std::promise ma jeszcze jedno zastosowanie, można go wykorzystać jako mechanizm do sygnalizacji pomiędzy wątkami (std::promise<void> - typ obiektu jako void). Coś na kształt std::condition_variable, z tym że taka sygnalizacja może zadziać się tylko raz. Aby odblokować pracę na współdzielonych danych należy wywołać set_value(). Aby wstrzymać pracę wątku do momentu, aż dane będą dostępne, należy wywołać metodę wait().
Przykład:
#include <iostream>
#include <thread>
#include <future>
using namespace std;
struct Fibonacci {
std::promise<int> p;
bool isSatisfied;
const int index;
int partialIndex;
Fibonacci(std::promise<int> p_, int index_)
: p(std::move(p_))
, isSatisfied(false)
, index(index_)
, partialIndex(0)
{
}
int fibonacci(int n)
{
std::cout << "Current n: " << n << std::endl;
int result = 0;
if (n == 0)
result = 0;
else if (n == 1)
result = 1;
else
result = fibonacci(n - 1) + fibonacci(n - 2);
if (isSatisfied == false and partialIndex == n) {
isSatisfied = true;
std::cout << "Passing partial result" << std::endl;
p.set_value(result);
}
return result;
}
void operator()(int partialVal_)
{
partialIndex = partialVal_;
fibonacci(index);
}
};
int main()
{
std::promise<int> p;
std::future<int> result = p.get_future();
std::thread t{ Fibonacci{ std::move(p), 5 }, 3 };
std::cout << "Result: " << result.get() << std::endl;
t.join();
return 0;
}
Przykładowy wynik:Current n: 5 Current n: 4 Current n: 3 Current n: 2 Current n: 1 Current n: 0 Current n: 1 Passing partial result Current n: 2 Current n: 1 Current n: 0 Current n: 3 Current n: 2 Result: 2Current n: 1 Current n: 0 Current n: 1Bibliografia:
- [1] Scotta Meyersa: Skuteczny nowoczesny C++. APN PROMISE SA, 2015. Rozdział 7, str. 289.
- [2] Anthony Williams: C++ Concurency in Action. USA Manning publications Co., 2012. Rozdział 7, str. 67.
Brak komentarzy:
Prześlij komentarz