Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Зачем нужна Boost.Coroutine


Автор: bsa 5.1.2013, 13:11
В http://www.boost.org/doc/libs/1_53_0_beta1/ будет включена библиотека coroutine. Прочитав описания стало понятно, как ее использовать, и как она работает. Но для меня пока остается загадкой ответ на вопрос: А ЗАЧЕМ? Единственное, что я могу предположить - деление больших неделимых операций на несколько маленьких. И это все? Может еще зачем-то она нужна и мне это позарез необходимо, но я не понимаю?
Одно точно я вижу, структура программы, использующей ее, значительно усложнится. И без внятной документации понять принцип работы программы будет ой как затруднительно. Подобное происходит, например, с asio. Но там хоть понятно - поставил задачу, до нее дошла очередь, она выполнилась. А тут? Работаешь, сохранил контекст, прыгнул (восстановил другой контекст) в середину другой функции, поработал, прыгнул в середину третьей...  smile 

Автор: NoviceF 5.1.2013, 15:49
фанаты goto давно ждали возможности для реванша  smile 

Автор: bsa 6.1.2013, 13:40
Цитата(NoviceF @  5.1.2013,  16:49 Найти цитируемый пост)
фанаты goto давно ждали возможности для реванша
 smile 
Реванш слишком жестокий...
Кстати, хочется услышать мнение boostcoder на этот счет - он сильно радовался этой либе.

Автор: maxim1000 6.1.2013, 20:31
если не злоупотреблять, штука довольно полезная
как минимум удобно при обработке последовательностей, при конвертации всяких данных, в некоторых алгоритмах (особенно тех, которые являются комбинацией нескольких сложных кусков)

представим себе, что у нас есть последовательность 123456789101112131415... (цифры всех чисел записаны друг за другом), нужно посчитать сумму всех элементов на чётных местах до 1000-го.

вывести символы последовательности одним циклом по номеру числа - проще простого, посчитать искомую сумму другим циклом по номеру символа - ещё проще

проблемы:
1. предварительно считать может будть накладно по памяти
2. далеко не для всех алгоритмов можно заранее сказать, сколько понадобится элементов без запуска алгоритма

самый простой выход - объединить два куска кода, т.е. вместо закидывания во временное хранилище мы просто отбрасываем нечётные, а чётные складываем

проблемы:
1. для одного алгоритма естественен цикл по числам, для второго - по символам, что бы мы ни выбрали, код одного из них станет сложнее
2. переменые, отвечающие за состояния алгоритмов (аккумулятор, номер числа) доступны обоим алгоритмам
3. алгоритм генерации последовательности и вычисления искомого значения, по-хорошему, не должны знать ничего друг о друге, тогда легче станет менять последовательности и вычисления независимо
4. плохо масштабируется (ведь может быть целая цепочка преобразований последовательностей)

следующий выход - сконвертировать один из алгоритмов во что-то вроде функтора и дёргать его из другого, пример (не для вышеописанной задачи) - std::istream_iterator и какой-нибудь алгоритм из namespace std, можно даже и то, и то, оставив главный цикл одиноким в своей функции (std::istream_iterator, std::find_if, функтор для отсева)

в принципе, этот вариант решает все проблемы, но получается довольно-таки неудобным, например , генерировать последовательность, описанную в начале, по одному символу неудобно - нужно больше переменных состояния, код сложнее, да и производительность похуже, с вычислением суммы тоже нужна дополнительная переменная появляется

а в случае с coroutines что построение последовательности, что вычисление искомой суммы представляются обычными циклами, циклы эти находятся в разных функциях, друг о друге почти ничего не знают, переменные состояния скрыты, всё хорошо

P.S.
с Boost.Coroutines я не работал, подробностей не знаю, описывал общее понятие coroutines

Автор: EvilsInterrupt 6.1.2013, 23:54
maxim1000
Если не сложно, то можно еще один пример где пригодится эта новая фича? Я привык понимать на нескольких.

Автор: boostcoder 7.1.2013, 14:04
Цитата(bsa @  6.1.2013,  13:40 Найти цитируемый пост)
хочется услышать мнение boostcoder

я дал слово, не использовать этот форум по назначению. так, некоторые свои темы буду обновлять.

зы
на самом деле, в связи с беспределом администрации сего ресурса(от чем я неоднократно писал http://forum.vingrad.ru/forum/forum-316.html), мне множество раз предлагали сменить ресурс, многие из тех кто еще недавно пользовался этим форумом и перестали.

Автор: NoviceF 7.1.2013, 14:34
 smile а чего было то, чего было?!?!

Автор: maxim1000 7.1.2013, 14:35
Цитата(EvilsInterrupt @  6.1.2013,  23:54 Найти цитируемый пост)
Если не сложно, то можно еще один пример где пригодится эта новая фича?

хм...
ну например, вот:

есть у нас какая-то древовидная структура из каких-то объектов
у каждого объекта есть цвет
нам нужно пробежаться по объектам и последовательность цветов запаковать как-нибудь (то ли RLE, то ли Zip)

самая простая пробежка по дереву - рекурсивная
для запаковки, как правило удобно просто бежать циклом по входному потоку

coroutines позволяют запихнуть пробежку по дереву в отдельную функцию и выдавать по одному значению, а запаковку - в другую, с простым циклом по значениям

Автор: Pfailed 7.1.2013, 14:58
Сопрограммы можно эффективно использовать в сетевом программировании. Предположим в одной функции вы произвели неблокирующий connect, следующая операция в этой функции -- запись в приконнекченный сокет. Естественно пока сокет не законнектится записывать бессмысленно, соответственно имеет смысл на это время переключиться в другую функцию и выполнить другое полезное действие. Таким образом получаем эффективное приложение и все в рамках одного процесса и потока.

Автор: EvilsInterrupt 7.1.2013, 15:51
Цитата

coroutines позволяют запихнуть пробежку по дереву в отдельную функцию и выдавать по одному значению, а запаковку - в другую, с простым циклом по значениям 

Очень похоже на yeld в генераторах Python и др. современных скриптовых языках.

Автор: baldina 7.1.2013, 23:07
Цитата(bsa @  5.1.2013,  13:11 Найти цитируемый пост)
Одно точно я вижу, структура программы, использующей ее, значительно усложнится.

Цитата(bsa @  5.1.2013,  13:11 Найти цитируемый пост)
А тут? Работаешь, сохранил контекст, прыгнул (восстановил другой контекст) в середину другой функции, поработал, прыгнул в середину третьей...

с точностью до наоборот. вы слишком вникаете в технику исполнения, а следует думать концептуально.
технически: сопрограмма имеет состояние, в которое возвращается при каждом последующем вызове. вызов завершается "оператором" возврата yield

Цитата(EvilsInterrupt @  7.1.2013,  15:51 Найти цитируемый пост)
Очень похоже на yeld в генераторах Python

да.
можно сказать, что сопрограммы реализуют yield, а остальное вытекает из этого факта.
точно также можно сказать, что сопрограммы имеют состояние и множество точек входа, из которых конкретная определяется состоянием. из этого вытекает возможность реализации yield.

сопрограммы - всего лишь инструмент, без него можно жить, но есть ситуации, когда использование сопрограмм дает более короткие и ясные программы.
обычно выделяют три ситуации:
1. конечные автоматы. здесь польза прямо вытекает из определения: сопрограмма меняет состояния автоматически, без привлечения специальных механизмов. пример:
для реализации автомата, который безусловно переходит в состояния 1->2->3 достаточно написать функцию-сопрограмму
Код

int getState () {
  for (int i=1; i <= 3; ++i)
    yield (i); // здесь сопрограмма возвращает значение (состояние автомата); при следующем вызове getState() продолжится с точки возврата, т.е. при текущем значении i
}

в boost::coroutine все не так лаконично
Код

int getState (coro::generator<int>::self& self) {
  int i=1;
  while (i <= 3) {
    self.yield (i++);
 }
 return i;
}

но идея ясна; это значительно экономичнее и прозрачнее конструкции
Код

int getState () {
  static int state = 0;
  switch (state) {
    case 0: return 1;
    case 1: return 2;
    case 2: return 3;
    default: throw (invalid_state());
  }
}

а если надо иметь несколько контекстов, вместо static int state потребуется целый огород.

2. генераторы. функции, генерирующие последовательность. возвращаемое значение можно интерпретировать как итератор.
Код

int fac () {
  int i=0;
  int f=1;
  while (true) {
    yield (f*=i++);
  }
}

int main () {
  for (int i=0; i < 10; ++i)
    cout << "Factorial " << i << "=" << fac() << endl;
}

в boost::coroutine и с итераторами:
Код

int fac_generator(generator_type::self& self)
{
  int i=0;
  int p=1;
  while (true) {
    p *= i++; 
    yield (p);
  }
  return p;
}

int main () {
  generator_type generator  (boost::bind  (fac_generator, _1));
  for (int i=0; i < 10; ++i) 
    cout<<"Factorial " << i << "=" << *generator++<<endl;
}

немного похоже на fsm, правда? действително, fsm - обобщение генераторов
в С++ без сопрограмм в простейшем случае нужно поддерживать контейнер для исходящих значений либо лишние параметры, в более сложном - слишкоммногабукф


3. модель взаимодействия акторов (actors). похоже на взаимодействие объектов в ООП (посылкой сообщений\вызовом методов), но отличается независимостью взаимодействующих единиц (вплоть до их параллельного исполнения). это модель типа источник/приемник (producer/consumer), ближайшая аналогия - межсетевое взаимодействие (по протоколу).
т.к. акторы (actors) являются сопрограммами, каждая может "думать" в удобных ей единицах: пакетах, запросах и т.п..
это как раз тот пример, который привел maxim1000. более классический пример - взаимодействие лексического и синтаксического анализатора: каждый является функцией, они исполняются параллельно, но у каждого свои "вехи": лексическому анализатору удобно мыслить в терминах лексем, синтаксическому - в терминах конструкций.


Автор: volatile 8.1.2013, 00:07
baldina, все замечательно, только ваши примеры делают не то что написано, если я не ошибаюсь конечно.

Цитата(baldina @  7.1.2013,  23:07 Найти цитируемый пост)
int fac () {
  int i=1;
  while (true) {
    yield (i*i++);
  }
}

Это же не генератор факториалов, а генератор квадратов, или что-то типа того...

Цитата(baldina @  7.1.2013,  23:07 Найти цитируемый пост)
int fac_generator(generator_type::self& self)
{
  int i=1;
  while (true) {
    i *= i+1; 
    yield (i);
  }
  return i;
}

И это тоже не генератор факториалов.

ген. факториалов будет как-то так:
Код

int fac () {
  int i = 0;
  int prod = 1;
  while (true) {
    yield (prod *= ++ i);
  }
}

Извините что вмешиваюсь, но примеры сбивают с толку просто.

Автор: bsa 8.1.2013, 00:09
Цитата(baldina @  8.1.2013,  00:07 Найти цитируемый пост)
yield (i*i++);
Исправь, это же типичное UB.
В целом, я пока не проникся... Что-то гложат меня сомнения об эффективности смены контекста (значений всех регистров) по сравнению с вызовом функции (которая может быть встроена, если компилятор это посчитает разумным).
Работа с потоками уже, вроде, решена в ASIO. По сути, там тоже самое, только через вызовы функций. Хотя, может в случае парсеров все несколько иначе - я ими не особо занимался.
Проблема генераторов решается путем выноса их контекста за пределы функций (типичный класс), причем, это будет эффективнее сопрограмм - нет восстановлений регистров процессора.
Я неправ?

Автор: baldina 8.1.2013, 00:38
Цитата(volatile @  8.1.2013,  00:07 Найти цитируемый пост)
Это же не генератор факториалов

да. был пьян, поторопился)))
ну, суть ясна, я думаю

Добавлено через 13 минут и 51 секунду
bsa, все это так.

Цитата(bsa @  8.1.2013,  00:09 Найти цитируемый пост)
Что-то гложат меня сомнения об эффективности смены контекста (значений всех регистров) по сравнению с вызовом функции

еще раз повторю: дело не в низкоуровневой реализации, а в высокоуровневной интерпретации, в подходе. это парадигма.
ясно, что контекст можно обернуть в класс, можно передать в качестве параметра.

вопрос не в том, как реализовать фичу, а как реализовать удобным образом.

как я понимаю, yield это часть концепции функционального подхода (и хотя в явном виде далеко не во всех функциональных языках присутствует, три описанные ситуации так или иначе в функциональных языках обычно реализуемы).

сопрограммы естественно реализуемы на ассемблере (как это описано у Кнута), а в языках, где есть лишь подпрограммы, реализация требует известных ухищрений.

можно использовать или не использовать ООП, обобщенное программирование. можно обойтись без мультиметодов, сравнения с образцом, делегатов и лямбда-функций и т.д. Однако имея эти средства, можно что-то делать яснее и короче. Только и всего.

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

Автор: baldina 8.1.2013, 01:04
м-да, с примерами не очень. исправил  smile

Добавлено через 7 минут и 32 секунды
Цитата(bsa @  8.1.2013,  00:09 Найти цитируемый пост)
это будет эффективнее сопрограмм - нет восстановлений регистров процессора

bsa, мне даже неловко вам говорить о преждевременной оптимизации...
но вообще говоря, любое высокоуровневое решение может повлечь дополнительные накладные расходы. при этом (часто незначительный, по сути) проигрыш в производительности компенсируется выигрышем в затратах на разработку и сопровождение.
в примерах Кнута (на MIXе) накладных расходов нет вообще: сопрограмма отличается от подпрограммы лишь тем, что адрес возврата, помещаемый в стек, всегда разный.

Автор: bsa 8.1.2013, 09:53
Цитата(baldina @  8.1.2013,  02:04 Найти цитируемый пост)
компенсируется выигрышем в затратах на разработку и сопровождение.

честно говоря, пока я не прочитал еще раз документацию, я не мог понять, как работает генератор из твоего примера. Не уверен, что эта фича упростит понимание программы. Чтобы ее понимать, нужно знать о ней.

Автор: baldina 8.1.2013, 15:14
Цитата(bsa @  8.1.2013,  09:53 Найти цитируемый пост)
Чтобы ее понимать, нужно знать о ней.

 smile 

что такое массив и список, чем они похожи и отличаются, где они хороши и плохи и т.д. должен знать каждый. так?
что такое подпрограммы - тем более. про сопрограммы - из того же разряда.
если бы вопрос крутился не вокруг библиотеки С++, а вокруг языка, непосредственно поддерживающего сопрограммы, вопроса скорее всего просто не возникло бы.

bsa, пока не прочитать про ООП, программисту С непонятно что такое класс, как оно работает и т.п. 
тут то же самое.
я отнюдь не хвалю boost::coroutine и не агитирую за испольование сопрограмм. да и примеры, согласен, лучше специально подготовленные, а не на коленке. но тогда уж надо открывать учебники и читать про концепции, преимущества и пр. 
в книгах это всегда полнее, чем любой ответ на форуме.



Автор: bsa 8.1.2013, 17:42
baldina, думаю ты согласишься с утверждением, что хороший язык программирования отличается от плохого помимо всего прочего ясностью кода. Действия break, return, continue, do, while, if, else и goto вполне очевидны. А вот yield... Ну ладно.
Я понял, что существуют задачи, где использование сопрограмм имеет смысл. Другое дело, что я таких не припомню.

Автор: volatile 8.1.2013, 18:38
Цитата(baldina @  8.1.2013,  01:04 Найти цитируемый пост)
м-да, с примерами не очень. исправил  

baldina, Еще раз извиняюсь за занудство, исправьте уж нормально, а то теперь у вас генератор нулей получился.

bsa, ну это как рекурсия. любую задачу можно решить без нее, согласитесь.
Но встречаются задачи (изредка) которые с рекурсией получаются гораздо короче и понятней.
С этим точно также.

Автор: bsa 8.1.2013, 19:40
volatile, я не спорю.

Автор: chaos 7.3.2013, 13:49
Чото нифига пока понять не могу.

Вопрос по первому же рисунку из доки
user posted image
Что нужно написать в main что бы получить такой же оутпут?

Автор: bsa 7.3.2013, 18:15
Там речь идет о том, как такое сделать.

Автор: EvilsInterrupt 31.3.2013, 12:18
Может кому-то пригодится. Для чего и зачем применять сопрограммы(coroutine). На мой взгляд достаточно лаконично и понятно описано в справочнике по Python Девид Бизли 4. изд. 2010 г.

Пояснения : стр.136 - Замыкания, декораторы, генераторы, сопрограммы
Примеры использования: стр.146(генераторами), стр.42, стр.147 (сопрограммы)

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)