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