Po co std::async

1

Hej
Po co wprowadzili std::async jak jest std::thread? Dzisiaj się natknąłem na to i tak czytam o tym i czytam i nie rozumiem po co to. Przy wątkach mam metodę join która oczekuje na zakończenie wątku, więc jest wystarczająca a jak chcę coś przekazać z wątku do wątku głównego to poprzez referencję std::ref z parametrów funkcji wątku można przekazać. Ktoś może natknął sie w necie na jakiś dobry tutorial wytłumaczający po co to zostało wprowadzone abym mógł zrozumieć?

1
fvg napisał(a):

Hej
Po co wprowadzili std::async jak jest std::thread?

Dla wygody. Podobnie jest z std::array czy std::lock_guard. Często lepiej "zaufać" bibliotece standardowej i nie wynajdować koła na nowo.

5

Bo czasem masz na tyle dobrze zdefiniowaną robotę, że możesz sobie bezpiecznie wykonać ją w osobnym wątku bez konieczności pilnowania wątku jako takiego. Wrzucasz i zapominasz, a jak potrzebujesz rezultat to sobie pobierasz futurem. Dzięki async masz mniej kodu do napisania. std::thread to niskopoziomowe narzędzie potrzebne gdy Twój system od początku jest projektowany jako system współbieżny i potrzebujesz większej kontroli nad wątkami.

3

W zasadzie koledzy wyżej już napisali, ale ujmę to trochę inaczej. Na programowanie asynchroniczne możesz patrzeć z punktu widzenia wątków albo z punktu widzenia zadań, to jakby 2 osobne paradygmaty. To pierwsze pojawiło się w programowaniu najpierw, bo reprezentowało to w jaki sposób programowanie asynchroniczne jest na procesorach realizowane. Jednak programisty tak naprawdę nie muszą interesować wątki - interesują go zadania, które program ma równolegle wykonać. Dlatego powstało to drugie podejście, w którym podstawowymi narzędziami są std::async, std::promise i std::future, a wątki leżą poziom abstrakcji niżej. W podobny sposób programowanie ewoluowało już wcześniej, np paradygmat proceduralny, bardziej odpowiadający sposobowi wykonywania programu (procedury to po prostu skoki do miejsc w pamięci) zastąpiono paradygmatem obiektowym, który odpowiada temu w jaki sposób reprezentuje się rzeczywiste problemy w programowaniu.

0

Jeżeli chcesz przeczytać IMHO bardzo dobry artykuł na ten temat, to klikinij tutaj

1

A skąd pomysł, że to musi być w osobnym wątku?

" In other words, f may be executed in another thread or it may be run synchronously when the resulting std::future is queried for a value."
https://en.cppreference.com/w/cpp/thread/async

Jak ktoś wyżej powiedział, to operacja na wyższym poziomie abstrakcji. Definiujesz zadanie, które może być wykonane niezależnie od głównego wątku. Może pójść zarówno w tle (np. w jakimś working thread) jak i jako lazy calculations.

C++ jest jak Ogry, ma warstwy. Działasz na wyższym poziomie abstrakcji, chyba że nie możesz, to wtedy przechodzisz na niższy Bjarne S.

1
fvg napisał(a):

Po co wprowadzili std::async jak jest std::thread? Dzisiaj się natknąłem na to i tak czytam o tym i czytam i nie rozumiem po co to. Przy wątkach mam metodę join która oczekuje na zakończenie wątku, więc jest wystarczająca a jak chcę coś przekazać z wątku do wątku głównego to poprzez referencję std::ref z parametrów funkcji wątku można przekazać.

Tak naprawdę nie chodzi o poziomy abstrakcji tylko o to, by nie tworzyć gigantycznej liczby wątków i nie operować metodami blokującymi (takimi jak wspomniany join). Tworzenie wątku trwa znacznie dłużej niż tworzenie zadania (tzn tego std::async) i analogicznie wątek jest dużo cięższy niż zadanie (nie tylko trzeba zrobić miejsce na osobny stos dla każdego wątku, ale też OS scheduler ma więcej roboty jak jest więcej wątków). Stąd robi się względnie niewielkie pule wątków (w porównaniu do liczby zadań na nich wykonywanych) na których kolejkuje się zadania. Wątek z puli po obsłużeniu danego zadania może obsługiwać kolejne. Jeśli jakieś zadanie zacznie blokować wątek (np wywoływać metodę sleep, wait, etc) to zmniejszy to liczbę wątków (z puli) dostępnych dla zadań nieblokujących.

0

Tworzenie wątku trwa znacznie dłużej niż tworzenie zadania (tzn tego std::async)

@Wibowit co masz na myśli przez zadanie? std::async to dość prostackie narzędzie, w tej chwili wykona zadaną funkcję albo w osobnym, nowym wątku, albo w wątku głównym w momencie pobrania wartości z std::future. Jest proste w użyciu, ale wcale nie gwarantuje lekkości, bo standard niejako utrudnia jego implementację jako pulę wątków. Tak było przynajmniej do C++14, nie orientowałem się jak to wygląda w C++17 czy 20.

1

Tworzenie wątku trwa znacznie dłużej niż tworzenie zadania.

Wszystko zależy w jaki sposób wywoływany jest std::async, a dokładnie od parametru policy.

  1. W domyślnym wywołaniu używany jest launch::async|launch::deferred, który przenosi odpowiedzialność za utworzenie nowego wątku na funkcje systemu operacyjnego. W zależności od dostępności zasobów (liczba rdzeni procesorów, liczba działających wątków itp. ) kod zostanie zrealizowany w nowym lub głównym wątku.
  2. Jeżeli parametrem wywołania będzie launch::deferred to nie będzie tworzony nowy wątek - w tym wypadku czas utworzenia zadania będzie krótszy.
  3. Dla parametru launch::async zawsze będzie tworzony nowy wątek. W tym przypadku czas będzie podobny do sytuacji, w której wątek tworzymy manualnie.
1

OK, przyznam się, że optymistycznie założyłem, że std::async działa jak programowanie asynchroniczne z innych języków, które znam. Jeśli std::async tworzy nowy wątek przy każdym odpaleniu to jakoś nie widzę specjalnej przewagi nad użyciem zwykłej std::function. Trochę mniej kodu, bo nie trzeba tego wątku explicite odpalać, ale poza tym raczej niewiele to daje (no nie?).

W takiej np Javie mamy metody typu:

1 użytkowników online, w tym zalogowanych: 0, gości: 1