Какие сопрограммы в C ++ 20?
Чем он отличается от «Parallelism2» или / и «Concurrency2» (см. Изображение ниже)?
Изображение ниже от ISOCPP.
На абстрактном уровне Coroutines отделил идею наличия состояния выполнения от идеи наличия потока выполнения.
SIMD (одна команда, несколько данных) имеет несколько «потоков выполнения», но только одно состояние выполнения (оно работает только с несколькими данными). Возможно, параллельные алгоритмы немного похожи на это в том, что у вас есть одна «программа», запущенная на разных данных.
Потоки имеют несколько «потоков выполнения» и несколько состояний выполнения. У вас есть более одной программы и более одного потока выполнения.
Coroutines имеет несколько состояний выполнения, но не владеет потоком выполнения. У вас есть программа, и программа имеет состояние, но у нее нет потока выполнения.
Самым простым примером сопрограмм являются генераторы или перечислимые элементы из других языков.
В псевдокоде:
function Generator() {
for (i = 0 to 100)
produce i
}
Generator
вызывается, и при первом вызове возвращается 0
, Его состояние запоминается (насколько состояние меняется в зависимости от реализации сопрограмм), и в следующий раз, когда вы его называете, оно продолжается там, где остановилось. Так что возвращается 1 в следующий раз. Тогда 2.
Наконец, он достигает конца цикла и падает с конца функции; сопрограмма закончена. (То, что здесь происходит, зависит от языка, о котором мы говорим; в python это вызывает исключение).
Сопрограммы приносят эту возможность в C ++.
Есть два вида сопрограмм; стека и без стека.
Сопрограмма без стеков хранит только локальные переменные в своем состоянии и месте выполнения.
Стековая сопрограмма хранит весь стек (например, поток).
Стеки-сопрограммы могут быть чрезвычайно легкими. Последнее предложение, которое я прочитал, касалось в основном переписывания вашей функции во что-то вроде лямбды; все локальные переменные переходят в состояние объекта, а метки используются для перехода в / из места, где сопрограмма «выдает» промежуточные результаты.
Процесс создания значения называется «yield», поскольку сопрограммы напоминают кооперативную многопоточность; Вы возвращаете точку исполнения обратно вызывающей стороне.
Boost имеет реализацию стековых сопрограмм; это позволяет вам вызывать функцию для вас. Stackful сопрограммы являются более мощными, но и более дорогими.
В сопрограммах есть нечто большее, чем простой генератор. Вы можете ожидать сопрограммы в сопрограмме, которая позволяет вам составлять сопрограммы в полезной манере.
Сопрограммы, такие как if, циклы и вызовы функций, представляют собой еще один вид «структурированного перехода», который позволяет более естественным образом выражать определенные полезные шаблоны (например, конечные автоматы).
Конкретная реализация Coroutines в C ++ немного интересна.
На самом базовом уровне он добавляет несколько ключевых слов в C ++: co_return
co_await
co_yield
вместе с некоторыми типами библиотек, которые работают с ними.
Функция становится сопрограммой, имея одну из них в своем теле. Таким образом, из их объявления они неотличимы от функций.
Когда одно из этих трех ключевых слов используется в теле функции, происходит некоторое стандартное обязательное исследование возвращаемого типа и аргументов, и функция превращается в сопрограмму. Это исследование сообщает компилятору, где хранить состояние функции, когда функция приостановлена.
Самая простая сопрограмма — это генератор:
generator<int> get_integers( int start=0, int step=1 ) {
for (int current=start; 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_line
Вероятно, это асинхронные сопрограммы, которые открывают файл и читают из него строки. co_await
связывает состояние ожидания и готовности load_data
к их прогрессу.
Сопрограммы C ++ гораздо более гибкие, чем эта, поскольку они были реализованы как минимальный набор языковых функций поверх типов пользовательского пространства. Типы пользовательского пространства эффективно определяют, что co_return
co_await
а также co_yield
имею в виду — Я видел, как люди используют его для реализации монадических необязательных выражений, так что 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;
}
Сопрограмма похожа на функцию C, которая имеет несколько операторов return и при вызове во 2-й раз не начинает выполнение с начала функции, но с первой инструкции после предыдущего выполненного возврата. Это место выполнения сохраняется вместе со всеми автоматическими переменными, которые будут находиться в стеке в функциях, не связанных с сопрограммами.
В предыдущей экспериментальной реализации сопрограммы от Microsoft использовались скопированные стеки, чтобы вы могли даже вернуться из глубоко вложенных функций. Но эта версия была отклонена комитетом C ++. Вы можете получить эту реализацию, например, с помощью библиотеки волокон Boosts.
Предполагается, что сопрограммы (в C ++) являются функциями, которые способны «ждать» завершения какой-либо другой подпрограммы и предоставлять все необходимое для продолжения приостановленной, приостановленной, ожидающей подпрограммы. Особенность, которая наиболее интересна для людей C ++, состоит в том, что сопрограммы в идеале не занимают места в стеке … C # уже может делать что-то подобное с await и yield, но для его включения в C ++ может потребоваться перестройка.
Параллелизм в значительной степени сосредоточен на разделении интересов, когда проблема — это задача, которую должна выполнить программа. Такое разделение интересов может быть достигнуто несколькими способами … обычно делегированием какого-либо рода. Идея параллелизма заключается в том, что ряд процессов может выполняться независимо (разделение задач), и «слушатель» будет направлять то, что создается этими разделенными задачами, туда, куда он должен идти. это сильно зависит от какого-то асинхронного управления. Существует несколько подходов к параллелизму, включая Аспектно-ориентированное программирование и другие. В C # есть оператор «делегат», который работает довольно хорошо.
параллелизм звучит как параллелизм и может быть задействован, но на самом деле это физическая конструкция, включающая множество процессоров, расположенных более или менее параллельно с программным обеспечением, способным перенаправлять части кода на разные процессоры, где он будет выполняться, и результаты будут получены обратно. синхронно.