Очень хорошее и понятное введение в сопрограммы - это презентация Джеймса Макнеллиса «Введение в сопрограммы C ++» (Cppcon2016)
philsumuru
2
Наконец, было бы неплохо затронуть тему «Чем сопрограммы в C ++ отличаются от реализаций сопрограмм и возобновляемых функций в других языках?» (который не рассматривается в указанной выше статье в Википедии, будучи независимым от языка)
Бен Фойгт,
1
кто еще читал этот "карантин в C ++ 20"?
Сахиб Яр,
Ответы:
203
На абстрактном уровне сопрограммы отделяют идею состояния выполнения от идеи наличия потока выполнения.
SIMD (одна инструкция с несколькими данными) имеет несколько «потоков выполнения», но только одно состояние выполнения (он просто работает с несколькими данными). Возможно, параллельные алгоритмы немного похожи на это в том смысле, что у вас есть одна «программа», работающая с разными данными.
У потоковой передачи есть несколько «потоков выполнения» и несколько состояний выполнения. У вас есть более одной программы и более одного потока выполнения.
Сопрограммы имеют несколько состояний выполнения, но не владеют потоком выполнения. У вас есть программа, и у нее есть состояние, но у нее нет потока выполнения.
Самый простой пример сопрограмм - это генераторы или перечисления из других языков.
В псевдокоде:
function Generator(){for(i =0 to 100)
produce i
}
В GeneratorНазывается, и первый раз , когда он назвал ее возвращает 0. Его состояние запоминается (насколько состояние меняется в зависимости от реализации сопрограмм), и в следующий раз, когда вы его вызываете, оно продолжается с того места, где было остановлено. Так что в следующий раз он вернет 1. Потом 2.
Наконец, он достигает конца цикла и падает с конца функции; сопрограмма готова. (То, что здесь происходит, зависит от языка, о котором мы говорим; в python это вызывает исключение).
Сопрограммы привносят эту возможность в C ++.
Есть два вида сопрограмм; стопки и без стопки.
Бестековая сопрограмма хранит только локальные переменные в своем состоянии и месте выполнения.
В стековой сопрограмме хранится весь стек (например, поток).
Бестековые сопрограммы могут быть очень легкими. Последнее предложение, которое я прочитал, касалось в основном переписывания вашей функции во что-то вроде лямбды; все локальные переменные переходят в состояние объекта, а метки используются для перехода в / из места, где сопрограмма «производит» промежуточные результаты.
Процесс создания значения называется «yield», поскольку сопрограммы немного похожи на кооперативную многопоточность; вы возвращаете точку исполнения вызывающему.
Boost имеет реализацию стековых сопрограмм; он позволяет вам вызывать функцию, которая будет уступать вам. Стековые сопрограммы более мощные, но и более дорогие.
Сопрограммы - это больше, чем простой генератор. Вы можете ожидать сопрограмму в сопрограмме, что позволяет вам составлять сопрограммы удобным способом.
Сопрограммы, такие как if, циклы и вызовы функций, представляют собой еще один вид «структурированного перехода», который позволяет более естественным образом выражать определенные полезные шаблоны (например, конечные автоматы).
Конкретная реализация Coroutines на C ++ немного интересна.
На самом базовом уровне он добавляет несколько ключевых слов в C ++: co_returnco_awaitco_yieldвместе с некоторыми типами библиотек, которые с ними работают.
Функция становится сопрограммой, имея одну из них в своем теле. Таким образом, по их объявлению они неотличимы от функций.
Когда одно из этих трех ключевых слов используется в теле функции, происходит стандартная обязательная проверка типа возвращаемого значения и аргументов, и функция преобразуется в сопрограмму. Эта проверка сообщает компилятору, где сохранить состояние функции, когда функция приостановлена.
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_returnco_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;}
Это одно из самых ясных объяснений того, что такое сопрограммы, которое я когда-либо читал. Сравнивать их и отличать от SIMD и классических нитей было отличной идеей.
Omnifarious
2
Я не понимаю пример дополнительных опций. std :: optional <int> не является ожидаемым объектом.
Джайв Дадсон
1
@mord да он должен возвращать 1 элемент. Может потребоваться полировка; если нам нужно более одной строки, нужен другой поток управления.
Якк - Адам Неврамонт
1
@lf извини, должно было быть ;;.
Yakk - Adam Nevraumont
1
@LF для такой простой функции может и нет разницы. Но разница, которую я вижу в целом, заключается в том, что сопрограмма запоминает точку входа / выхода (выполнения) в своем теле, тогда как статическая функция каждый раз запускает выполнение с начала. Полагаю, расположение "локальных" данных не имеет значения.
avp
21
Сопрограмма похожа на функцию C, которая имеет несколько операторов возврата и при вызове во второй раз запускает выполнение не в начале функции, а с первой инструкции после предыдущего выполненного возврата. Это место выполнения сохраняется вместе со всеми автоматическими переменными, которые будут находиться в стеке в функциях, не являющихся сопрограммами.
Предыдущая экспериментальная реализация сопрограмм от Microsoft действительно использовала скопированные стеки, поэтому вы могли даже вернуться из глубоко вложенных функций. Но эта версия была отклонена комитетом C ++. Вы можете получить эту реализацию, например, с помощью библиотеки волокон Boosts.
сопрограммы должны быть (в C ++) функциями, которые могут «ждать» завершения какой-либо другой подпрограммы и предоставлять все необходимое для продолжения приостановленной, приостановленной, ожидающей подпрограммы. особенность, которая наиболее интересна для C ++ людей, заключается в том, что сопрограммы в идеале не занимали бы места в стеке ... C # уже может делать что-то подобное с await и yield, но C ++, возможно, придется перестроить, чтобы получить это.
параллелизм в значительной степени сосредоточен на разделении задач, когда проблема - это задача, которую программа должна выполнить. такое разделение ответственности может быть достигнуто несколькими способами ... обычно это какое-то делегирование. Идея параллелизма состоит в том, что ряд процессов может выполняться независимо (разделение задач), а «слушатель» направляет все, что создается этими разделенными задачами, туда, куда это предполагается. это сильно зависит от некоторого вида асинхронного управления. Существует несколько подходов к параллелизму, включая аспектно-ориентированное программирование и другие. В C # есть оператор «делегат», который работает довольно хорошо.
параллелизм звучит как параллелизм и может быть задействован, но на самом деле это физическая конструкция, включающая множество процессоров, расположенных более или менее параллельно, с программным обеспечением, которое может направлять части кода на разные процессоры, где он будет выполняться, и результаты будут получены обратно синхронно.
Параллелизм и разделение ответственности совершенно не связаны. Сопрограммы не предоставляют информацию для приостановленной процедуры, они являются возобновляемыми подпрограммами.
Ответы:
На абстрактном уровне сопрограммы отделяют идею состояния выполнения от идеи наличия потока выполнения.
SIMD (одна инструкция с несколькими данными) имеет несколько «потоков выполнения», но только одно состояние выполнения (он просто работает с несколькими данными). Возможно, параллельные алгоритмы немного похожи на это в том смысле, что у вас есть одна «программа», работающая с разными данными.
У потоковой передачи есть несколько «потоков выполнения» и несколько состояний выполнения. У вас есть более одной программы и более одного потока выполнения.
Сопрограммы имеют несколько состояний выполнения, но не владеют потоком выполнения. У вас есть программа, и у нее есть состояние, но у нее нет потока выполнения.
Самый простой пример сопрограмм - это генераторы или перечисления из других языков.
В псевдокоде:
В
Generator
Называется, и первый раз , когда он назвал ее возвращает0
. Его состояние запоминается (насколько состояние меняется в зависимости от реализации сопрограмм), и в следующий раз, когда вы его вызываете, оно продолжается с того места, где было остановлено. Так что в следующий раз он вернет 1. Потом 2.Наконец, он достигает конца цикла и падает с конца функции; сопрограмма готова. (То, что здесь происходит, зависит от языка, о котором мы говорим; в python это вызывает исключение).
Сопрограммы привносят эту возможность в C ++.
Есть два вида сопрограмм; стопки и без стопки.
Бестековая сопрограмма хранит только локальные переменные в своем состоянии и месте выполнения.
В стековой сопрограмме хранится весь стек (например, поток).
Бестековые сопрограммы могут быть очень легкими. Последнее предложение, которое я прочитал, касалось в основном переписывания вашей функции во что-то вроде лямбды; все локальные переменные переходят в состояние объекта, а метки используются для перехода в / из места, где сопрограмма «производит» промежуточные результаты.
Процесс создания значения называется «yield», поскольку сопрограммы немного похожи на кооперативную многопоточность; вы возвращаете точку исполнения вызывающему.
Boost имеет реализацию стековых сопрограмм; он позволяет вам вызывать функцию, которая будет уступать вам. Стековые сопрограммы более мощные, но и более дорогие.
Сопрограммы - это больше, чем простой генератор. Вы можете ожидать сопрограмму в сопрограмме, что позволяет вам составлять сопрограммы удобным способом.
Сопрограммы, такие как if, циклы и вызовы функций, представляют собой еще один вид «структурированного перехода», который позволяет более естественным образом выражать определенные полезные шаблоны (например, конечные автоматы).
Конкретная реализация Coroutines на C ++ немного интересна.
На самом базовом уровне он добавляет несколько ключевых слов в C ++:
co_return
co_await
co_yield
вместе с некоторыми типами библиотек, которые с ними работают.Функция становится сопрограммой, имея одну из них в своем теле. Таким образом, по их объявлению они неотличимы от функций.
Когда одно из этих трех ключевых слов используется в теле функции, происходит стандартная обязательная проверка типа возвращаемого значения и аргументов, и функция преобразуется в сопрограмму. Эта проверка сообщает компилятору, где сохранить состояние функции, когда функция приостановлена.
Самая простая сопрограмма - это генератор:
co_yield
приостанавливает выполнение функций, сохраняет это состояние вgenerator<int>
, а затем возвращает значениеcurrent
черезgenerator<int>
.Вы можете перебирать возвращенные целые числа.
co_await
тем временем позволяет вам соединять одну сопрограмму с другой. Если вы находитесь в одной сопрограмме и вам нужны результаты ожидаемой вещи (часто сопрограммы) перед тем, как продолжить, выco_await
в ней. Если они готовы, приступайте немедленно; в противном случае вы приостанавливаете работу до тех пор, пока ожидаемый объект не будет готов.load_data
сопрограмма, которая генерируетstd::future
когда именованный ресурс открывается, и нам удается выполнить синтаксический анализ до точки, где мы нашли запрошенные данные.open_resource
иread_line
s, вероятно, являются асинхронными сопрограммами, открывающими файл и считывающими из него строки.co_await
Соединяет Приостановка и состояние готовностиload_data
к их прогрессу.Сопрограммы C ++ гораздо более гибкие, чем это, поскольку они были реализованы как минимальный набор языковых функций поверх типов пользовательского пространства. Типы пользовательского пространства эффективно определяют, что
co_return
co_await
иco_yield
означают - я видел, как люди использовали его для реализации монадических необязательных выражений, таких как aco_await
для пустого необязательного параметра автоматически распространяет пустое состояние на внешний необязательный:вместо того
источник
;;
.Сопрограмма похожа на функцию C, которая имеет несколько операторов возврата и при вызове во второй раз запускает выполнение не в начале функции, а с первой инструкции после предыдущего выполненного возврата. Это место выполнения сохраняется вместе со всеми автоматическими переменными, которые будут находиться в стеке в функциях, не являющихся сопрограммами.
Предыдущая экспериментальная реализация сопрограмм от Microsoft действительно использовала скопированные стеки, поэтому вы могли даже вернуться из глубоко вложенных функций. Но эта версия была отклонена комитетом C ++. Вы можете получить эту реализацию, например, с помощью библиотеки волокон Boosts.
источник
сопрограммы должны быть (в C ++) функциями, которые могут «ждать» завершения какой-либо другой подпрограммы и предоставлять все необходимое для продолжения приостановленной, приостановленной, ожидающей подпрограммы. особенность, которая наиболее интересна для C ++ людей, заключается в том, что сопрограммы в идеале не занимали бы места в стеке ... C # уже может делать что-то подобное с await и yield, но C ++, возможно, придется перестроить, чтобы получить это.
параллелизм в значительной степени сосредоточен на разделении задач, когда проблема - это задача, которую программа должна выполнить. такое разделение ответственности может быть достигнуто несколькими способами ... обычно это какое-то делегирование. Идея параллелизма состоит в том, что ряд процессов может выполняться независимо (разделение задач), а «слушатель» направляет все, что создается этими разделенными задачами, туда, куда это предполагается. это сильно зависит от некоторого вида асинхронного управления. Существует несколько подходов к параллелизму, включая аспектно-ориентированное программирование и другие. В C # есть оператор «делегат», который работает довольно хорошо.
параллелизм звучит как параллелизм и может быть задействован, но на самом деле это физическая конструкция, включающая множество процессоров, расположенных более или менее параллельно, с программным обеспечением, которое может направлять части кода на разные процессоры, где он будет выполняться, и результаты будут получены обратно синхронно.
источник