Как можно реализовать сопрограммы в языке C++

Я сомневаюсь, что это можно сделать переносимо, но есть ли какие-либо решения? Я думаю,что это можно сделать, создав альтернативный стек и сбросив SP, BP и IP при входе в функцию, и имея выход сохранить IP и восстановить SP+BP. Деструкторы и безопасность исключений кажутся сложными, но разрешимыми.

Это было сделано? Это невозможно?

17 ответов


да можно сделать без проблем. Все, что вам нужно, это небольшой код сборки для перемещения стека вызовов в недавно выделенный стек в куче.

Я посмотреть boost:: coroutine библиотека.

единственное, что вы должны следить за переполнения стека. В большинстве операционных систем переполнение стека вызовет segfault, поскольку страница виртуальной памяти не отображается. Однако, если вы выделите стек на вы не получите никаких гарантий. Просто имейте это в виду.


в POSIX вы можете использовать процедуры makecontext()/swapcontext() для переносимого переключения контекстов выполнения. В Windows можно использовать fiber API. В противном случае все, что вам нужно, это немного кода сборки клея, который переключает контекст машины. Я реализовал coroutines как с ASM (для AMD64), так и с swapcontext (); ни один из них не очень сложный.


для потомков,

Дмитрий Вьюков по веб-сайт чудесная есть хитрый трюк, используя ucontext и setjump для имитации сопрограммы в языке C++.

помимо этого, библиотека Оливер Kowalke был в последнее время принято в Boost, поэтому, надеюсь, мы увидим обновленную версию boost.coroutine, который работает на x86_64 в ближайшее время.


нет простого способа реализовать coroutine. Потому что сама coroutine находится вне абстракции стека C/C++, как и поток. Таким образом, он не может быть поддержан без изменений уровня языка для поддержки.

В настоящее время (C++11) все существующие реализации C++ coroutine основаны на взломе уровня сборки, который трудно быть безопасным и надежным пересечением платформ. Чтобы быть надежным, он должен быть стандартным и обрабатываться компиляторами, а не взломом.

там стандартное предложение-N3708 для этого. Проверьте, если вам интересно.


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

Это может сделать вещи более ремонтопригодны. Другой разработчик C++ может не сразу понять сопрограмму, тогда как они могут быть более знакомы с итератором.


Я не думаю, что есть много полномасштабных, чистых реализаций в C++. Одна попытка, которая мне нравится, это protothread библиотека.


тут COROUTINE портативная библиотека C++ для последовательности coroutine указать вам в правильном направлении? Это похоже на элегантное решение, которое продлилось испытание временем.....ему 9 лет!

в папке DOC находится pdf-файл бумаги портативная библиотека C++ для последовательностей Coroutine от Keld Helsgaun, которая описывает библиотеку и предоставляет короткие примеры ее использования.

[update] я на самом деле успешно использую его сам. Любопытство взяло лучше меня, поэтому я посмотрел на это решение и обнаружил, что оно хорошо подходит для проблемы, над которой я работал в течение некоторого времени!


для тех, кто хочет знать, как они могут использовать Coroutines переносным способом на C++, вам придется подождать C++17. Комитет по стандартам работает над функцией см. бумага N3722. Чтобы суммировать текущий проект документа, вместо Async и Await ключевые слова будут возобновляться и ждать.

взгляните на экспериментальную реализацию в Visual Studio 2015, чтобы играть с экспериментальной реализацией Microsoft. Не похоже. у clang еще есть реализация.

есть хороший разговор от Cppcon Coroutines отрицательная абстракция накладных описать преимущества использования сопрограмм в C++ и как это влияет на простоту и производительность кода.

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

обновление: Похоже, реализация coroutine не переходит в C++17, но она будет быть технической спецификацией (p0057r2). С другой стороны, похоже, что их поддержка в clang с флагом-fcoroutines_ts и в Visual Studio 2015 Update 2. К ключевым словам также добавляется co_. Итак, co_await, co_yield и т. д.


новая библиотека, импульс.Контекст, был выпущен сегодня с портативными функциями для реализации coroutines.


Это старый поток, но я хотел бы предложить взломать устройство Даффа, которое не зависит от ос (насколько я помню):

C coroutines с помощью устройства Даффа

и в качестве примера, вот библиотека telnet, которую я изменил, чтобы использовать coroutines вместо fork / threads: библиотека cli Telnet с использованием coroutines

и поскольку стандартный C до C99 по существу является истинным подмножеством C++, это хорошо работает и в C++.


Он основан на макросах (cringe), но следующий сайт предоставляет простую в использовании реализацию генератора:http://www.codeproject.com/KB/cpp/cpp_generators.aspx


Я придумал реализацию без asm код. Идея состоит в том, чтобы использовать функцию создания потока системы для инициализации стека и контекста и использовать setjmp/longjmp для переключения контекста. Но это не портативный, см. tricky pthread версия если вы заинтересованы.


https://github.com/tonbit/coroutine является C++11 синглом .H асимметричная реализация корутина, поддерживающая примитивы resume/yield / await и модель канала. Он реализует через ucontext/fiber, не зависящий от boost, работающий на linux/windows / macOS. Это хорошая отправная точка для изучения реализации coroutine на c++.


Проверьте мою реализацию, она иллюстрирует точку взлома asm и проста:

https://github.com/user1095108/generic/blob/master/coroutine.hpp


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

но, может быть, вы хотите использовать сопрограммы, возможно, потому, что у вас есть хорошо проверенный алгоритм, который уже написан и протестирован, или потому, что вы переносите код, написанный таким образом.

Если вы работаете в Windows, вы должны взглянуть на волокнами. Волокна дадут вам структуру, похожую на корутин, с поддержкой ОС.

Я не знаком с другими ОС, чтобы рекомендовать альтернативы там.


WvCont является частью WvStreams, который реализует так называемый полу-сопрограммы. С ними немного легче справиться, чем с полноценными сорутинами: вы взываете к ним, и они возвращаются к человеку, который их вызвал.

он реализован с использованием более гибкой WvTask, которая поддерживает полные сопрограммы; вы можете найти его в той же библиотеке.

работает на win32 и Linux, по крайней мере, и, вероятно, любой другой системе Unix.


Я пытался реализовать сопрограммы себя с использованием C++11 и нитей:

#include <iostream>
#include <thread>

class InterruptedException : public std::exception {
};

class AsyncThread {
public:
    AsyncThread() {
        std::unique_lock<std::mutex> lock(mutex);
        thread.reset(new std::thread(std::bind(&AsyncThread::run, this)));
        conditionVar.wait(lock); // wait for the thread to start
    }
    ~AsyncThread() {
        {
            std::lock_guard<std::mutex> _(mutex);
            quit = true;
        }
        conditionVar.notify_all();
        thread->join();
    }
    void run() {
        try {
            yield();
            for (int i = 0; i < 7; ++i) {
                std::cout << i << std::endl;
                yield();
            }
        } catch (InterruptedException& e) {
            return;
        }
        std::lock_guard<std::mutex> lock(mutex);
        quit = true;
        conditionVar.notify_all();
    }
    void yield() {
        std::unique_lock<std::mutex> lock(mutex);
        conditionVar.notify_all();
        conditionVar.wait(lock);
        if (quit) {
            throw InterruptedException();
        }
    }
    void step() {
        std::unique_lock<std::mutex> lock(mutex);
        if (!quit) {
            conditionVar.notify_all();
            conditionVar.wait(lock);
        }
    }
private:
    std::unique_ptr<std::thread> thread;
    std::condition_variable conditionVar;
    std::mutex mutex;
    bool quit = false;
};

int main() {
    AsyncThread asyncThread;
    for (int i = 0; i < 3; ++i) {
        std::cout << "main: " << i << std::endl;
        asyncThread.step();
    }
}