Co to są programy w C ++ 20?


104

Co to są programy w ?

Czym różni się od „Parallelism2” i / lub „Concurrency2” (spójrz na poniższą ilustrację)?

Poniższy obraz pochodzi z ISOCPP.

https://isocpp.org/files/img/wg21-timeline-2017-03.png

wprowadź opis obrazu tutaj


3
Aby odpowiedzieć: „W jaki sposób koncepcja koreutynów różni się od równoległości i współbieżności ?” - en.wikipedia.org/wiki/Coroutine
Ben Voigt


3
Bardzo dobrym i łatwym do naśladowania wstępem do programu jest prezentacja Jamesa McNellisa „Introduction to C ++ Coroutines” (Cppcon2016).
philsumuru

2
Na koniec dobrze byłoby również omówić "Czym różnią się programy w C ++ od implementacji programów i funkcji wznawialnych w innych językach?" (czego nie dotyczy powyższy link w artykule wikipedii, będąc agnostykiem językowym, nie dotyczy)
Ben Voigt

1
kto jeszcze czytał tę „kwarantannę w C ++ 20”?
Sahib Yar

Odpowiedzi:


203

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
}

GeneratorNazywa, 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_yieldwraz 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_yieldzawiesza wykonywanie funkcji, zapisuje ten stan w generator<int>, a następnie zwraca wartość currentpoprzez generator<int>.

Możesz zapętlić zwrócone liczby całkowite.

co_awaitw 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_awaitdalej. 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_datajest programem, który generuje a, std::futuregdy nazwany zasób jest otwierany i udaje nam się przeanalizować do punktu, w którym znaleźliśmy żądane dane.

open_resourcea read_lines to prawdopodobnie procedury asynchroniczne, które otwierają plik i odczytują z niego wiersze. Stan co_awaitwstrzymania i gotowości łączy z load_dataich 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_awaiti co_yield znaczą - widziałem, jak ludzie używają go do implementacji monadycznych wyrażeń opcjonalnych, tak że a co_awaitna 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;
}

26
To jedno z najwyraźniejszych wyjaśnień, jakie są programy, jakie kiedykolwiek czytałem. Porównanie ich i odróżnienie od SIMD i klasycznych wątków było świetnym pomysłem.
Omnifarious

2
Nie rozumiem przykładu opcji dodatków. std :: optional <int> nie jest obiektem oczekiwanym.
Jive Dadson

1
@mord tak, ma zwrócić 1 element. Może wymagać polerowania; jeśli chcemy, aby więcej niż jedna linia wymagała innego przepływu sterowania.
Yakk - Adam Nevraumont

1
@lf przepraszam, powinno być ;;.
Yakk - Adam Nevraumont

1
@LF dla tak prostej funkcji może nie ma różnicy. Ale różnica, którą widzę ogólnie, polega na tym, że program coroutine zapamiętuje punkt wejścia / wyjścia (wykonania) w swoim ciele, podczas gdy funkcja statyczna za każdym razem rozpoczyna wykonywanie od początku. Wydaje mi się, że lokalizacja danych „lokalnych” nie ma znaczenia.
avp

21

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.


1

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.


9
Współbieżność i rozdzielenie obaw nie są ze sobą całkowicie powiązane. Programy nie dostarczają informacji o zawieszonej procedurze, procedurami, które można wznowić.
Ben Voigt
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.