Обновить

Комментарии 11

Если углубиться в теорию, то выяснится что существует несколько разновидностей продолжений. Наиболее распространённая из них - неограниченные продолжения, представляющие состояние программы в определённый момент. Вызов такого продолжения не похож на вызов функции, поскольку он соответствует переходу в сохраненное состояние программы и не возвращает никакого значения. Такое продолжение обычно нельзя вызвать несколько раз. 

Какой смысл в продолжении, которое нельзя вызывать несколько раз, и где реализовано такое чудо?

Смысл в том, чтобы можно было перейти из одной функции в другую, например из планировщика сопрограмм в корутину, запланированную к выполнению. В качестве примера реализации можно привести язык Си, где есть функция setjmp для сохранения состояния выполнения программы и longjmp для перехода к сохраненному состоянию. Неограниченное продолжение пришло из языка Scheme, где реализовывалось при помощи функции call/cc. В качестве второго примера можно назвать библиотеку Boost Context, где есть функция callcc, представляющая собой аналог call/cc из Scheme.

Я спрашивал про это:

которое нельзя вызывать несколько раз

В Scheme продолжение можно вызывать сколько угодно.

Постараюсь объяснить на примере функций setjmp/longjmp
int setjmp(jmp_buf env) - сохраняет состояние программы, устанавливая точку возврата при помощи структуры типа jmp_buf.
Возвращаемое значение этой функции зависит от контекста. Если функция setjmp вызывается впервые, она возвращает 0. Если управление передается обратно в setjmp из longjmp, то возвращается ненулевое значение аргумента val функции longjmp.
Функция void longjmp(jmp_buf env, int val) выполняет переход в точку, заданную ранее с помощью setjmp. При этом восстанавливается сохраненное состояние, и выполнение продолжается с места, где был сделан вызов setjmp. Функция longjmp передает управление обратно в функцию, которая вызывала setjmp и с этого места продолжается выполнение программы.
Параметр val представляет значение, которое будет возвращено из функции setjmp при восстановлении состояния программы - своего рода статус выполнения. Оно должно быть отличным от нуля, чтобы отличить этот случай от первого вызова setjmp. Если же это значение устновить равным 0, то все равно setjmp возвращает 1. Сделано это для того чтобы предотвратить переход программы в бесконечный цикл. Рассмотрим пример:

jmp_buf env;

void test() {
    puts("В функции test перед longjmp.");
    longjmp(env, 1);  // Возвращаемся к точке, где был вызван setjmp
    puts("Эта строка не будет выведена.");
}
 
int main() {
    if (setjmp(env) == 0) {   // Точка возврата из longjmp
        // Первый вызов setjmp возвращает 0 при нормальном вызове
        puts("Выполняем код до вызова longjmp.");
        test();  // В этой функции будет вызван longjmp
    } else {
        // Второй вызов - longjmp возвращает ненулевое значение
        puts("Возвращение из longjmp");
    }
    return 0;
}

Если бы можно было при помощи longjmp заставить setjmp повторно вернуть 0, то программа бы зациклилась.

Ну ваша программа просто написана так, что зацикливается при повторном возврате 0. В действительности никуда не денутся изменённые значения глобальных переменных и прочего окружения, на которые программа могла бы ориентироваться. Даже безусловный goto сам по себе не означает зацикливания программы.

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

Select упомянут в тексте статьи, но в примерах программ его нет. Есть только иллюстрация использования неблокирующего режима работы файлового дескриптора в корутине. В будущем планирую отдельную статью в которой будет рассмотрено использование epoll с сопрограммами на примере простого tcp сервера.

"Протопоток" - это ещё одно название для user space thread? А как же fiber или goroutine? Я так понял единственное их отличе это то что они stackless.

"Протопоток" относится к user space thread. Но я не считаю эти пониятия тождественными так как fiber и goroutine также относятся к user space thread. Да, "протопотоки" stackless и используются, в первую очередь, в языке Си для написания программ в системах с сильно ограниченным объемом памяти типа микроконтроллеров. Что касается fiber, то тут надо уточнить. Есть библиотека Fiber для С++, входящая в Boost. Там Fiber stackful. Есть еще библиотека Fiber в Windows. Я с ней не работал, но вроде они тоже stackful, как описано здесь. Горутины не для Си/С++, но тоже stackful.

MD победил программиста на Си/С++? Раз уж блоки кода настолько отвратные хоть бы сделали ссылки на godbolt c рабочими примерами

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации