2015-06-12 14 views
20

Jestem trochę zmieszany z powodu funkcji std::async.Zamieszanie dotyczące wątków uruchamianych przez std :: async z std :: launch :: parametr async

W specyfikacji podano: operacja asynchroniczna wykonywana "jak w nowym wątku wykonania" (C++ 11 § 30.6.8/11).

Co to ma znaczyć?

W moim rozumieniu, kod

std::future<double> fut = std::async(std::launch::async, pow2, num); 

powinien uruchomić funkcję pow2 na nowym wątku i przekazać zmienną num do wątku przez wartość, wówczas kiedyś w przyszłości, gdy funkcja jest wykonywana, miejsce wynik w postaci fut (o ile funkcja pow2 ma podpis podobny do double pow2(double);). Ale specyfikacja mówi "jak gdyby", co sprawia, że ​​całość jest dla mnie mglista.

Pytanie brzmi:

Czy nowy wątek zawsze uruchomiony w tym przypadku? Mam nadzieję, że tak. Dla mnie parametr std::launch::async ma sens w taki sposób, że wyraźnie stwierdzam, że rzeczywiście chcę stworzyć nowy wątek.

I kod

std::future<double> fut = std::async(std::launch::deferred, pow2, num); 

powinny leniwa ewaluacja możliwe, poprzez opóźnianie połączenia pow2 funkcji w punkcie, w którym piszę coś takiego var = fut.get();. W tym przypadku parametr std::launch::deferred powinien oznaczać, że wyraźnie stwierdzam, że nie chcę nowego wątku, po prostu chcę się upewnić, że funkcja zostanie wywołana, gdy będzie potrzebna jego wartość zwracana.

Czy moje założenia są prawidłowe? Jeśli nie, proszę wyjaśnić.

Również wiem, że domyślnie funkcja nazywa się następująco:

std::future<double> fut = std::async(std::launch::deferred | std::launch::async, pow2, num); 

W tym przypadku, powiedziano mi, że to, czy nowy wątek zostanie uruchomiony czy nie, zależy od implementacji. Ponownie, co to ma znaczyć?

+4

„jakby” oznacza, że ​​może teoretycznie ponowne wykorzystanie istniejącego wątku (np w puli wątków) tak długo, jak zachowanie jest nie do odróżnienia. W praktyce, bardzo niewiele implementacji (jeśli takowa) robi to, ponieważ "tak, jakby nowy wątek" wymaga zniszczenia i odtworzenia wszystkich zmiennych lokalnych wątku. –

+0

@ T.C. lub implementuj (ciężkie) zmienne lokalne typu coroutine. Niech każdy wątek otrzyma domyślny coroutine, a 'thread_local' jest coroutine local. 'Asynchroniczny' może utworzyć współprogram, że coroutine może zostać przeniesiony do innego wątku i uruchomiony. Tj., Emulować wątków (z coroutines) na górze dostarczonego przez system wątku? – Yakk

+1

Uważam, że 'std :: async' jest zepsuty, czy to prawda? –

Odpowiedz

23

Szablon funkcji std::async (część szablonu <future>) służy do uruchamiania (prawdopodobnie) zadania asynchronicznego. Zwraca obiekt std::future, który ostatecznie zatrzyma wartość zwracaną przez funkcję parametru std::async.

Gdy wartość jest potrzebna, wywołujemy metodę get() na instancji std::future; blokuje to wątek, aż przyszłość będzie gotowa, a następnie zwróci wartość. std::launch::async lub std::launch::deferred można określić jako pierwszy parametr dla std::async w celu określenia sposobu uruchomienia zadania.

  1. std::launch::async wskazuje, że wywołanie funkcji musi być uruchomione na osobnym (nowym) wątku. (Weź pod uwagę komentarz użytkownika @ T.C.).
  2. std::launch::deferred oznacza, że ​​wywołanie funkcji ma zostać odroczone do czasu wywołania wait() lub get() w przyszłości. Własność przyszłości może zostać przeniesiona do innego wątku, zanim to nastąpi.
  3. std::launch::async | std::launch::deferred wskazuje, że wdrożenie może wybrać. Jest to domyślna opcja (jeśli nie określisz osobiście). Może zdecydować się na uruchomienie synchroniczne.

Czy w tym przypadku zawsze pojawiał się nowy wątek?

Od 1., możemy powiedzieć, że nowy wątek jest zawsze uruchamiany.

Czy moje założenia [na std :: launch :: odłożone] są prawidłowe?

Od 2. Możemy powiedzieć, że twoje założenia są poprawne.

Co to ma znaczyć? [W odniesieniu do nowego wątku jest uruchomiona lub nie w zależności od implementacji]

Od 3., jak std::launch::async | std::launch::deferred to opcja domyślna, oznacza to, że realizacja funkcji szablonu std::async zadecyduje, czy będzie tworzyć nowy wątek lub nie. Dzieje się tak dlatego, że niektóre implementacje mogą być sprawdzane przez nadmierne planowanie.

UWAGA

Poniższa sekcja nie jest związane z pytaniem, ale myślę, że ważne jest, aby pamiętać.

Standard C++ mówi, że jeśli std::future zawiera ostatnie odniesienie do stanu współdzielonego odpowiadającego wywołaniu funkcji asynchronicznej, to destruktor std :: future musi blokować, dopóki wątek dla asynchronicznie uruchomionej funkcji nie zostanie zakończony. Instancja std::future zwrócona przez std::async będzie zatem blokować w swoim destruktorze.

void operation() 
{ 
    auto func = [] { std::this_thread::sleep_for(std::chrono::seconds(2)); }; 
    std::async(std::launch::async, func); 
    std::async(std::launch::async, func); 
    std::future<void> f{ std::async(std::launch::async, func) }; 
} 

To mylące kod może sprawić, że rozmowy std::async są asynchroniczne, w rzeczywistości są synchroniczne. Instancje std::future zwrócone przez std::async są tymczasowe i będą blokować, ponieważ ich destruktor jest wywoływany prawidłowo, gdy zwraca się do nich, ponieważ nie są one przypisane do zmiennej.

Pierwsze połączenie z std::async zablokuje się na 2 sekundy, a następnie kolejne 2 sekundy zablokowania od drugiego połączenia do std::async. Możemy myśleć, że ostatnie wywołanie do std::async nie blokuje, ponieważ przechowujemy zwróconą instancję std::future w zmiennej, ale ponieważ jest to zmienna lokalna, która jest zniszczona na końcu zakresu, to faktycznie zablokuje się na dodatkowe 2 sekundy na końcu zakresu funkcji, gdy zmienna lokalna f jest zniszczona.

Innymi słowy, wywoływanie funkcji operation() zablokuje synchronizowany wątek przez około 6 sekund. Takie wymagania mogą nie istnieć w przyszłej wersji standardu C++.

Źródła informacji Kiedyś skompilować te Uwagi:

C++ współbieżność w działaniu: Praktyczne wielowątkowość, Anthony Williams

Scott Meyers' blogu: http://scottmeyers.blogspot.ca/2013/03/stdfutures-from-stdasync-arent-special.html

+0

Aby wyjaśnić, jeśli zmienisz zwykłe wywołania 'std :: async' na' auto t1 = std :: async (...); 'w twoim przykładzie, czas blokady będzie wynosił tylko około 2 sekund, prawda? –

+0

@AdamHunyadi, jeśli chodzi o całkowity czas blokady - tak, ponieważ trzy różne nici spałyby jednocześnie. –

0

ja też mylić to i uruchomił szybki test w systemie Windows, który pokazuje, że przyszłość asynchroniczna zostanie uruchomiona na wątkach puli wątków systemu operacyjnego. Prosta aplikacja może to zademonstrować, a wyłuskanie w Visual Studio spowoduje również wyświetlenie wątków wykonawczych o nazwie "TppWorkerThread".

#include <future> 
#include <thread> 
#include <iostream> 

using namespace std; 

int main() 
{ 
    cout << "main thread id " << this_thread::get_id() << endl; 

    future<int> f1 = async(launch::async, [](){ 
     cout << "future run on thread " << this_thread::get_id() << endl; 
     return 1; 
    }); 

    f1.get(); 

    future<int> f2 = async(launch::async, [](){ 
     cout << "future run on thread " << this_thread::get_id() << endl; 
     return 1; 
    }); 

    f2.get(); 

    future<int> f3 = async(launch::async, [](){ 
     cout << "future run on thread " << this_thread::get_id() << endl; 
     return 1; 
    }); 

    f3.get(); 

    cin.ignore(); 

    return 0; 
} 

spowoduje wyjście podobny do:

main thread id 4164 
future run on thread 4188 
future run on thread 4188 
future run on thread 4188 
0

To w rzeczywistości nie jest prawdą. Dodaj thread_local zapamiętanej wartości i widać, że faktycznie std::async run f1 f2 f3 zadania w różnych wątkach, ale sama std::thread::id

Powiązane problemy