Co to są programy w c ++ 20?
Czym różni się od „Parallelism2” i / lub „Concurrency2” (spójrz na poniższą ilustrację)?
Poniższy obraz pochodzi z ISOCPP.
Co to są programy w c ++ 20?
Czym różni się od „Parallelism2” i / lub „Concurrency2” (spójrz na poniższą ilustrację)?
Poniższy obraz pochodzi z ISOCPP.
Odpowiedzi:
Na poziomie abstrakcyjnym, Coroutines oddzieliły ideę posiadania stanu wykonania od idei posiadania wątku wykonania.
SIMD (pojedyncza instrukcja wiele danych) ma wiele „wątków wykonania”, ale tylko jeden stan wykonania (działa tylko na wielu danych). Prawdopodobnie algorytmy równoległe są trochę podobne do tego, że jeden „program” działa na różnych danych.
Wątkowanie ma wiele „wątków wykonywania” i wiele stanów wykonywania. Masz więcej niż jeden program i więcej niż jeden wątek wykonania.
Coroutines ma wiele stanów wykonania, ale nie jest właścicielem wątku wykonania. Masz program, który ma stan, ale nie ma wątku wykonywania.
Najłatwiejszym przykładem coroutines są generatory lub wyliczenia z innych języków.
W pseudokodzie:
function Generator() {
for (i = 0 to 100)
produce i
}
Generator
Nazywa, a po raz pierwszy nazywa go zwraca 0
. Jego stan jest zapamiętywany (jak bardzo zmienia się stan w zależności od implementacji programów) i następnym razem, gdy go nazwiesz, będzie kontynuowany od miejsca, w którym został przerwany. Więc następnym razem zwraca 1. Następnie 2.
W końcu dociera do końca pętli i spada z końca funkcji; program jest zakończony. (To, co się tutaj dzieje, różni się w zależności od języka, o którym mówimy; w Pythonie zgłasza wyjątek).
Korekty wprowadzają tę możliwość do C ++.
Istnieją dwa rodzaje programów; stos i bez stosu.
Program bez stosu przechowuje tylko zmienne lokalne w swoim stanie i lokalizacji wykonania.
Sterty coroutine przechowuje cały stos (jak nić).
Korekty bez stosu mogą być niezwykle lekkie. Ostatnia propozycja, którą przeczytałem, polegała w zasadzie na przepisaniu twojej funkcji na coś podobnego do lambdy; wszystkie zmienne lokalne przechodzą do stanu obiektu, a etykiety służą do przeskakiwania do / z lokalizacji, w której program „generuje” wyniki pośrednie.
Proces tworzenia wartości nazywany jest „wydajnością”, ponieważ procedury są nieco podobne do wielowątkowości kooperatywnej; oddajesz punkt wykonania z powrotem dzwoniącemu.
Boost ma implementację stosowych procedur; pozwala wywołać funkcję, która da ci wynik. Układane w stosy programy są mocniejsze, ale także droższe.
Korekty to coś więcej niż prosty generator. Możesz poczekać na coroutine w coroutine, co pozwoli ci komponować coroutines w użyteczny sposób.
Korekty, takie jak if, pętle i wywołania funkcji, to inny rodzaj „strukturalnego goto”, który pozwala wyrazić pewne użyteczne wzorce (takie jak maszyny stanu) w bardziej naturalny sposób.
Specyficzna implementacja Coroutines w C ++ jest nieco interesująca.
Na najbardziej podstawowym poziomie dodaje kilka słów kluczowych do C ++: co_return
co_await
co_yield
wraz z pewnymi typami bibliotek, które z nimi współpracują.
Funkcja staje się rutyną, mając jedną z tych funkcji w swoim ciele. Tak więc z ich deklaracji nie da się ich odróżnić od funkcji.
Kiedy jedno z tych trzech słów kluczowych jest używane w treści funkcji, zachodzi pewne standardowe nakazane badanie typu zwracanego i argumentów, a funkcja jest przekształcana w procedurę. To badanie mówi kompilatorowi, gdzie przechowywać stan funkcji, gdy funkcja jest zawieszona.
Najprostszym programem jest generator:
generator<int> get_integers( int start=0, int step=1 ) {
for (int current=start; true; current+= step)
co_yield current;
}
co_yield
zawiesza wykonywanie funkcji, zapisuje ten stan w generator<int>
, a następnie zwraca wartość current
poprzez generator<int>
.
Możesz zapętlić zwrócone liczby całkowite.
co_await
w międzyczasie pozwala łączyć jeden program z drugim. Jeśli jesteś w jednym programie i potrzebujesz wyników czegoś nieoczekiwanego (często programu), zanim przejdziesz co_await
dalej. Jeśli są gotowe, kontynuujesz natychmiast; jeśli nie, zawieszasz, aż to, na co czekasz, będzie gotowe.
std::future<std::expected<std::string>> load_data( std::string resource )
{
auto handle = co_await open_resouce(resource);
while( auto line = co_await read_line(handle)) {
if (std::optional<std::string> r = parse_data_from_line( line ))
co_return *r;
}
co_return std::unexpected( resource_lacks_data(resource) );
}
load_data
jest programem, który generuje a, std::future
gdy nazwany zasób jest otwierany i udaje nam się przeanalizować do punktu, w którym znaleźliśmy żądane dane.
open_resource
a read_line
s to prawdopodobnie procedury asynchroniczne, które otwierają plik i odczytują z niego wiersze. Stan co_await
wstrzymania i gotowości łączy z load_data
ich postępem.
Korekty języka C ++ są znacznie bardziej elastyczne, ponieważ zostały zaimplementowane jako minimalny zestaw funkcji językowych na szczycie typów przestrzeni użytkownika. Typy przestrzeni użytkownika skutecznie definiują, co co_return
co_await
i co_yield
znaczą - widziałem, jak ludzie używają go do implementacji monadycznych wyrażeń opcjonalnych, tak że a co_await
na pustym opcjonalnym automatycznie propaguje stan pusty do zewnętrznego opcjonalnego:
modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
return (co_await a) + (co_await b);
}
zamiast
std::optional<int> add( std::optional<int> a, std::optional<int> b ) {
if (!a) return std::nullopt;
if (!b) return std::nullopt;
return *a + *b;
}
;;
.
Program jest podobny do funkcji w języku C, która ma wiele instrukcji powrotu i po drugim wywołaniu nie rozpoczyna wykonywania na początku funkcji, ale na pierwszej instrukcji po poprzednim wykonanym powrocie. Ta lokalizacja wykonania jest zapisywana razem ze wszystkimi zmiennymi automatycznymi, które żyłyby na stosie w funkcjach innych niż standardowe.
Poprzednia eksperymentalna implementacja programu firmy Microsoft wykorzystywała skopiowane stosy, dzięki czemu można było nawet powrócić z głęboko zagnieżdżonych funkcji. Ale ta wersja została odrzucona przez komisję C ++. Możesz uzyskać tę implementację na przykład z biblioteką włókien Boosts.
programy mają być (w C ++) funkcjami, które są w stanie „czekać” na zakończenie jakiejś innej procedury i dostarczać wszystko, co jest potrzebne, aby zawieszona, wstrzymana, oczekująca procedura mogła działać. cechą, która jest najbardziej interesująca dla ludzi C ++, jest to, że w idealnym przypadku programy nie zajmowałyby miejsca na stosie ... C # może już zrobić coś takiego z await i yield, ale C ++ może wymagać przebudowy, aby go uzyskać.
współbieżność jest silnie skoncentrowana na oddzieleniu problemów, gdy problem jest zadaniem, które program ma wykonać. to oddzielenie obaw można osiągnąć na wiele sposobów ... zwykle jest to pewnego rodzaju delegacja. idea współbieżności polega na tym, że pewna liczba procesów może działać niezależnie (oddzielenie problemów), a „słuchacz” kierowałby wszystko, co jest wytwarzane przez te oddzielne obawy, tam, gdzie ma to nastąpić. jest to silnie uzależnione od pewnego rodzaju zarządzania asynchronicznego. Istnieje wiele podejść do współbieżności, w tym programowanie zorientowane na aspekty i inne. C # ma operator „delegat”, który działa całkiem nieźle.
równoległość brzmi jak współbieżność i może być zaangażowana, ale w rzeczywistości jest fizyczną konstrukcją obejmującą wiele procesorów ułożonych w mniej lub bardziej równoległy sposób z oprogramowaniem, które jest w stanie kierować części kodu do różnych procesorów, gdzie zostanie uruchomiony, a wyniki zostaną zwrócone synchronicznie.