Пример сопрограммы
Приведем пример некоторой ситуации, где сопрограммы могут оказаться полезными. Вам предлагается напечатать последовательность действительных чисел в качестве ввода, но каждое восьмое число в выводе нужно опустить. Вывод должен представлять собой последовательность строк из шести чисел (кроме последней строки, если для ее заполнения чисел не хватает). Если in обозначает n-й элемент ввода, вывод выглядит так:
i1 i2 i3 i4 i5 i6 i7 i9 i10 i11 i12 i13 i14 i15 i17 и т. д.Наконец, вывод должен включать только 1000 чисел.
Эта задача характерна для использования сопрограмм. Она включает три процесса, каждый со своей специфической логикой: ввод, где требуется пропускать каждое восьмое число, вывод с ограничением строки до шести чисел, главная программа, где требуется обработать 1000 элементов. Традиционные структуры управления неудобны при сочетании процессов с разными ограничениями. Решение, основанное на сопрограммах, будет проходить гладко.
Введем три сопрограммы: producer (ввод), printer (вывод) и controller. Общая структура такова:
begin class PRODUCER begin ... См. далее ... end PRODUCER; class PRINTER begin ... См. далее ... end PRINTER; class CONTROLLER begin ... См. далее ... end CONTROLLER; ref (PRODUCER) producer; ref (PRINTER) printer; ref (CONTROLLER) controller; producer :- new PRODUCER; printer :- new PRINTER; controller :- new CONTROLLER; resume controller endЭто главная программа, в обычном смысле этого слова. Она создает экземпляр каждого из трех классов - соответствующую сопрограмму и продолжает одну из них - контроллер. Классы приведены далее:
class CONTROLLER; begin integer i; detach; for i := 1 step 1 until 1000 do resume printer end CONTROLLER; class PRINTER; begin integer i; detach; while true do for i := 1 step 1 until 8 do begin resume producer; outreal (producer.last_input); resume controller end; next_line end end PRINTER; class PRODUCER; begin integer i; real last_input, discarded; detach; while true do begin for i := 1 step 1 until 6 do begin last_input := inreal; resume printer end; discarded := inreal end end PRODUCER;Тело каждого класса начинается с detach, что позволяет главной программе продолжать инициализацию других сопрограмм.
Функция inreal возвращает число, прочитанное из входного потока, процедура outreal его печатает, процедура next_line обеспечивает переход на следующую строку ввода.
Сопрограммы хорошо соответствуют другим понятиям ОО-построения ПО. Заметим, насколько децентрализована приведенная схема: каждый процесс занимается своим делом, вмешательство других ограничено. Producer заботится о создании элементов ввода, printer - о выводе, controller - о том, когда начинать и заканчивать. Как обычно, хорошей проверкой качества решения является простота расширения и модификации; здесь явно надо добавить сопрограмму, проверяющую конец ввода (как просит одно из упражнений). Сопрограммы расширяют децентрализацию еще на один шаг, что является признаком хорошей ОО-архитектуры.
Архитектуру можно сделать еще более децентрализованной. В частности, процессы в описанной структуре должны все же активизировать друг друга по имени. В идеале им не нужно ничего знать друг о друге, кроме передаваемой информации (например, принтер получает last_input от producer). Примитивы моделирования, изучаемые далее, позволяют это. После этого решение может использовать полный механизм параллелизма, описанный в одной из лекций. Его независимость от платформы означает, что он будет работать для сопрограмм, так же как истинный параллелизм.