Що таке супроводи у C ++ 20?


104

У чому полягають супроводи ?

Чим він відрізняється від «Паралелізму2» або / та «Конкурсу2» (дивіться нижче на зображення)?

Зображення нижче від ISOCPP.

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

введіть тут опис зображення


3
Відповісти: " Чим поняття співпрограм відрізняється від паралелізму та одночасності ?" - en.wikipedia.org/wiki/Coroutine
Бен Войгт


3
Дуже вдалий та простий у виконанні вступ у програму - презентація Джеймса Мак-Нілліса «Вступ до судових процедур C ++» (Cppcon2016).
philsumuru

2
Нарешті, було б також добре висвітлити "Чим супроводи в C ++ відрізняються від реалізації інших функцій супроводу та відновлених функцій інших мов?" (котра вищезв'язана стаття у Вікіпедії, яка є агностиком, не звертається)
Ben Voigt

1
хто ще читав цей "карантин у С ++ 20"?
Сахіб Яр

Відповіді:


200

На абстрактному рівні Coroutines розділив ідею відключення стану виконання від ідеї наявності потоку виконання.

SIMD (одноразова множина даних інструкцій) має кілька "потоків виконання", але лише один стан виконання (він просто працює на декількох даних). Можливо, паралельні алгоритми трохи подібні до того, що у вас є одна "програма", що працює на різних даних.

Нитка має кілька "потоків виконання" та кілька станів виконання. У вас є більше однієї програми та більше ніж один потік виконання.

Супроводи мають кілька станів виконання, але не володіють потоком виконання. У вас є програма, і програма має стан, але вона не має нитки виконання.


Найпростіший приклад співпраць - це генератори чи перелічувачі з інших мов.

У псевдокоді:

function Generator() {
  for (i = 0 to 100)
    produce i
}

GeneratorНазивається, і перший раз , коли він назвав її повертає 0. Його стан запам'ятовується (наскільки стан залежить від впровадження процедур), і наступного разу, коли ви його зателефонуєте, він продовжується там, де він припинився. Таким чином, він повертається 1 наступного разу. Потім 2.

Нарешті вона доходить до кінця циклу і відпадає від кінця функції; поправка закінчена. (Те, що відбувається тут, залежить від мови, про яку ми говоримо; у python - це виняток).

Супротивники приводять цю здатність до C ++.

Існує два види корутин; складний і нестабільний.

Безпроблемна супровідна програма зберігає лише локальні змінні у своєму стані та місці виконання.

Складний інструмент зберігає цілий стек (як нитка).

Безрецептурні супроводи можуть мати надзвичайно малу вагу. Остання пропозиція, яку я прочитав, стосувалася, в основному, переписування вашої функції в щось схоже на лямбда; всі локальні змінні переходять у стан об'єкта, а мітки використовуються для переходу до / з місця, де підпрограма «виробляє» проміжні результати.

Процес отримання вартості називається "урожайність", оскільки супроводи трохи схожі на кооперативну багатопоточність; ви повертаєте точку виконання назад абоненту.

Boost має реалізацію складних процедур; це дозволяє викликати функцію, щоб приносити вам. Складні супроводи є більш потужними, але й дорожчими.


Співпраці більше, ніж простий генератор. Ви можете чекати на програмі в супровідній програмі, що дозволяє вам складати корисні програми корисним чином.

Супроводи, як-от if, циклі і функціонування дзвінків, - це ще один вид "структурованого goto", який дозволяє висловлювати певні корисні зразки (наприклад, державні машини) більш природним чином.


Конкретна реалізація Coroutines в C ++ є дещо цікавою.

На самому базовому рівні він додає декілька ключових слів до C ++: co_return co_await co_yieldразом із деякими типами бібліотек, які працюють з ними.

Функція стає супровідною програмою, маючи в своєму тілі одне з тих. Тож у своїй декларації вони не відрізняються від функцій.

Коли одне з цих трьох ключових слів використовується у функціональному тілі, відбувається деяке стандартне доручення вивчення типу повернення та аргументів, і функція перетворюється в супровід. Це вивчення повідомляє компілятору, де зберігати стан функції, коли функція призупинена.

Найпростіша програма - це генератор:

generator<int> get_integers( int start=0, int step=1 ) {
  for (int current=start; true; current+= step)
    co_yield current;
}

co_yieldпризупиняє виконання функцій, зберігає стан у generator<int>, а потім повертає значення currentчерез generator<int>.

Ви можете перевести цикл на повернені цілі числа.

co_awaitтим часом дозволяє скласти одну кореневу програму на іншу. Якщо ви перебуваєте в одній програмі, і вам потрібні результати очікуваної речі (найчастіше корупції), перш ніж прогресувати, ви co_awaitїї виконаєте . Якщо вони готові, ви продовжуєте негайно; якщо ні, ви призупиняєте, поки не буде готове очікування, яке вас чекає.

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- це посібник, який генерує, std::futureколи відкритий названий ресурс, і нам вдається проаналізувати точку, де ми знайшли запитувані дані.

open_resourceі read_lines - це, ймовірно, асинхронні підпрограми, які відкривають файл і читають з нього рядки. co_awaitПоєднує Призупинення та стан готовності load_dataдо їх прогресу.

Спроби C ++ набагато гнучкіші, ніж це, оскільки вони були реалізовані як мінімальний набір мовних функцій на вершині типів простору користувача. Типи простору користувача ефективно визначають, що co_return co_awaitі що co_yield означає - я бачив, як люди використовують його для реалізації монадичних необов'язкових виразів, щоб a co_awaitна порожній необов'язково автоматично пропонував порожній стан зовнішній необов'язковий:

modified_optional<int> add( modified_optional<int> a, modified_optional<int> b ) {
  return (co_await a) + (co_await b);
}

замість

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
Це одне з найяскравіших пояснень того, що таке супроводи, що я коли-небудь читав. Порівнювати їх та відрізняти їх від SIMD та класичних ниток було чудовою ідеєю.
всезначний

2
Я не розумію приклад додаткових додатків. std :: необов'язково <int> - це не очікуваний об’єкт.
Jive Dadson

1
@mord так, він повинен повернути 1 елемент. Можливо, потрібна полірування; якщо нам потрібно більше, ніж один рядок, потрібен інший потік управління.
Якк - Адам Невраумон

1
@lf вибачте, мав бути ;;.
Якк - Адам Невраумон

1
@LF для такої простої функції, можливо, різниці немає. Але в цілому я бачу різницю в тому, що спільна програма запам'ятовує точку входу / виходу (виконання) у своєму тілі, тоді як статична функція щоразу починає виконання з початку. Я думаю, розташування "локальних" даних не має значення.
AVP

21

Співпраця - це як функція C, яка має кілька операторів повернення, і коли вона викликається 2-й раз, не запускає виконання на початку функції, а в першій інструкції після попереднього виконаного повернення. Це місце збереження зберігається разом із усіма автоматичними змінними, які б існували на стеку в функціях, що не містять корекції.

У попередній експериментальній реалізації програми Microsoft використовували скопійовані стеки, щоб ви могли навіть повернутися з глибоко вкладених функцій. Але цю версію комітет C ++ відхилив. Ви можете отримати цю реалізацію, наприклад, з бібліотеки волокон Boosts.


1

припущення повинні бути функціями (в C ++), які здатні "чекати" завершення якоїсь іншої програми і надавати все необхідне для припинення, призупинення, очікування, розпорядку. Особливістю, яка є найбільш цікавою для людей із С ++, є те, що спрощення в ідеалі не займатимуть місця у стеці ... C # вже може зробити щось подібне з очікуванням та поступкою, але C ++, можливо, доведеться перебудувати для його введення.

паралельність сильно орієнтована на розмежування проблем, коли стурбованість є завданням, яке передбачається виконати програмою. це розділення проблем може бути здійснене кількома способами ... як правило, делегуванням. ідея одночасності полягає в тому, що низка процесів може протікати незалежно (розділення проблем), і «слухач» спрямовуватиме все, що виробляється тими окремими проблемами, куди не слід. це сильно залежить від якогось асинхронного управління. Існує ряд підходів до паралельності, включаючи програмування, орієнтоване на аспекти та інші. У C # є оператор «делегат», який працює досить непогано.

паралелізм звучить як паралельність і може бути задіяний, але насправді це фізична конструкція, що включає багато процесорів, розташованих більш-менш паралельно, з програмним забезпеченням, яке здатне направляти частини коду до різних процесорів, де він буде запущений, і результати будуть отримані назад синхронно.


9
Паралельність і розділення проблем абсолютно не пов'язані. Програми не повинні надавати інформацію для призупиненого розпорядку, вони є поновлюваними процедурами.
Ben Voigt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.