Как можно реализовать сопрограммы в языке 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();
}
}