Как вы реализуете Coroutines в С++
Я сомневаюсь, что это можно сделать портативно, но есть ли там какие-то решения? Я думаю, что это можно сделать, создав альтернативный стек и перезапустив SP, BP и IP для ввода функции и получив доступ к сохранению IP и восстановлению SP + BP. Деструкторы и безопасность исключений кажутся сложными, но разрешимыми.
Это сделано? Это невозможно?
Ответы
Ответ 1
Да, он может быть сделан без проблем. Все, что вам нужно, это небольшой код сборки, чтобы переместить стек вызовов в новый выделенный стек в куче.
Я бы посмотрел boost:: coroutine библиотека.
Единственное, на что вы должны обратить внимание, это переполнение стека. В большинстве операционных систем переполнение стека вызовет segfault, поскольку страница виртуальной памяти не отображается. Однако, если вы выделяете стек в кучу, вы не получаете никакой гарантии.
Просто имейте это в виду.
Ответ 2
В POSIX вы можете использовать подпрограммы makecontext()/swapcontext() для переносимости контекстов выполнения. В Windows вы можете использовать API-интерфейс волокна. В противном случае все, что вам нужно, это немного кода сборки клея, который переключает контекст машины. Я реализовал сопрограммы как с ASM (для AMD64), так и с swapcontext(); ни очень трудно.
Ответ 3
Для потомков
Дмитрий Вьюков wondeful web site имеет хитроумный трюк, используя ucontext и setjump для моделирования сопрограмм в С++.
Кроме того, контекстная библиотека Oliver Kowalke была недавно принята в Boost, поэтому, надеюсь, мы увидим обновленную версию boost.coroutine, которая работает на x86_64 в ближайшее время.
Ответ 4
Нет простого способа реализовать сопрограмму. Потому что сама сопрограмма вне абстракции стека C/C++, как поток. Поэтому его нельзя поддерживать без изменений уровня языка.
В настоящее время (C++ 11) все существующие реализации сопрограмм C++ основаны на взломе на уровне сборки, который трудно перестраховать и надежно пересекает платформы. Чтобы быть надежным, он должен быть стандартным и обрабатываться компиляторами, а не хакингом.
Существует стандартное предложение - N3708 для этого. Проверьте это, если вам интересно.
Ответ 5
Возможно, вам будет лучше с итератором, чем с сопрограммой, если это возможно. Таким образом, вы можете продолжить вызов next()
, чтобы получить следующее значение, но вы можете сохранить свое состояние как переменные-члены вместо локальных переменных.
Это может сделать вещи более удобными. Другой разработчик С++ может не сразу понять сопрограмму, тогда как они могут быть более знакомы с итератором.
Ответ 6
Для тех, кто хочет знать, как они могут использовать сопрограммы портативным способом в C++ y̶o̶u̶ ̶w̶i̶l̶l̶ ̶h̶a̶v̶e̶ ̶t̶o̶ ̶w̶a̶i̶t̶ ̶f̶o̶r̶ ̶C̶ + ̶ + ̶1̶7̶ ожидание закончено (см. ниже)! Комитет по стандартам работает над этой функцией, см. статью N3722. Чтобы подвести итоги текущего проекта статьи, вместо Async и Await ключевые слова будут возобновляемыми и ожидают.
Взгляните на экспериментальную реализацию в Visual Studio 2015, чтобы поиграть с экспериментальной реализацией Microsoft. Похоже, у clang еще есть реализация.
В Cppcon есть хорошая беседа о том, что сопрограммы об отрицательной служебной абстракции описывают преимущества использования сопрограмм в C++ и как это влияет на простоту и производительность кода.
В настоящее время мы все еще должны использовать библиотечные реализации, но в ближайшем будущем у нас будут сопрограммы в качестве основной функции C++.
Обновить:
Похоже, что реализация сопрограммы намечена для C++ 20, но была выпущена как техническая спецификация с C++ 17 (p0057r2). Visual C++, clang и gcc позволяют вам использовать флаг времени компиляции.
Ответ 7
Есть ли COROUTINE портативная библиотека С++ для последовательности coroutine указывает вам в правильном направлении? Это похоже на элегантное решение, которое длилось испытание временем... ему 9 лет!
В папке DOC находится pdf-документ из статьи A Portable С++ Library для Coroutine Sequencing от Keld Helsgaun, которая описывает библиотеку и предоставляет короткие примеры ее использования.
[обновление] Я сам успешно использую его. Любопытство улучшило меня, поэтому я рассмотрел это решение и нашел, что это хорошо подходит для проблемы, над которой я работал некоторое время!
Ответ 8
Я не думаю, что в C++ есть много полноценных, чистых реализаций. Одна попытка, которая мне нравится, это библиотека протоколов Адама Данкела.
См. также Protothreads: упрощение событийно-ориентированного программирования встроенных систем с ограниченным объемом памяти в цифровой библиотеке ACM и обсуждение в теме Википедии Protothread,
Ответ 9
Он основан на макросах (cringe), но следующий сайт обеспечивает простую в использовании реализацию генератора: http://www.codeproject.com/KB/cpp/cpp_generators.aspx
Ответ 10
Новая библиотека, Boost.Context, была выпущена сегодня с помощью переносимых функций для выполнения сопрограмм.
Ответ 11
Это старый поток, но я хотел бы предложить взломать устройство Duff, которое не зависит от ОС (насколько я помню):
C сопрограммы с использованием устройства Duff
И как пример, вот библиотека telnet, которую я модифицировал, чтобы использовать сопрограммы вместо fork/threads:
Библиотека telnet cli с помощью сопрограмм
И поскольку стандарт C до C99 по существу является истинным подмножеством С++, это хорошо работает и на С++.
Ответ 12
Я придумал код без asm. Идея состоит в том, чтобы использовать функцию создания системных потоков для инициализации стека и контекста и использовать setjmp/longjmp для переключения контекста. Но он не переносится, если вы заинтересованы, посмотрите сложную версию pthread.
Ответ 13
https://github.com/tonbit/coroutine - это синтаксическая реализация асимметричной сопроводительной версии С++ 11, поддерживающая примитивы resume/yield/wait и модель канала. Он реализует через ucontext/fiber, не зависящий от boost, работает на linux/windows/macOS. Это хорошая отправная точка для изучения реализации сопрограммы в С++.
Ответ 14
Проверьте мою реализацию, она иллюстрирует точку взлома asm и проста:
https://github.com/user1095108/generic/blob/master/coroutine.hpp
Ответ 15
Также на основе макросов (устройство Duff, полностью переносимое, см.
http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
и вдохновленный ссылкой, опубликованной Марком, следующее эмулирует совлокальные процессы, сотрудничающие с использованием событий в качестве механизма синхронизации (модель, немного отличающаяся от традиционного стиля сопрограмм/генератора)
// Coprocess.h
#pragma once
#include <vector>
class Coprocess {
public:
Coprocess() : line_(0) {}
void start() { line_ = 0; run(); }
void end() { line_ = -1; on_end(); }
virtual void run() = 0;
virtual void on_end() {};
protected:
int line_;
};
class Event {
public:
Event() : curr_(0) {}
void wait(Coprocess* p) { waiters_[curr_].push_back(p); }
void notify() {
Waiters& old = waiters_[curr_];
curr_ = 1 - curr_; // move to next ping/pong set of waiters
waiters_[curr_].clear();
for (Waiters::const_iterator I=old.begin(), E=old.end(); I != E; ++I)
(*I)->run();
}
private:
typedef std::vector<Coprocess*> Waiters;
int curr_;
Waiters waiters_[2];
};
#define corun() run() { switch(line_) { case 0:
#define cowait(e) line_=__LINE__; e.wait(this); return; case __LINE__:
#define coend default:; }} void on_end()
Пример использования:
// main.cpp
#include "Coprocess.h"
#include <iostream>
Event e;
long sum=0;
struct Fa : public Coprocess {
int n, i;
Fa(int x=1) : n(x) {}
void corun() {
std::cout << i << " starts\n";
for (i=0; ; i+=n) {
cowait(e);
sum += i;
}
} coend {
std::cout << n << " ended " << i << std::endl;
}
};
int main() {
// create 2 collaborating processes
Fa f1(5);
Fa f2(10);
// start them
f1.start();
f2.start();
for (int k=0; k<=100; k++) {
e.notify();
}
// optional (only if need to restart them)
f1.end();
f2.end();
f1.start(); // coprocesses can be restarted
std::cout << "sum " << sum << "\n";
return 0;
}
Ответ 16
Вы всегда должны использовать вместо этого использование потоков; особенно в современном оборудовании. Если у вас есть работа, которая может быть логически разделена в Co-подпрограммах, использование потоков означает, что работа может выполняться одновременно с помощью отдельных исполнительных блоков (процессорных ядер).
Но, возможно, вы хотите использовать сопрограммы, возможно, потому, что у вас есть хорошо протестированный алгоритм, который уже был написан и протестирован таким образом, или потому, что вы портируете написанный таким образом код.
Если вы работаете в Windows, вы должны взглянуть на волокна. Волокна предоставят вам сопроводительную структуру с поддержкой ОС.
Я не знаком с другими ОС, чтобы рекомендовать там альтернативы.
Ответ 17
WvCont является частью WvStreams, который реализует так называемые полукоротины. Их немного легче обрабатывать, чем полнофункциональные сопрограммы: вы вызываете его, и он возвращает обратно тому, кто его назвал.
Он реализован с использованием более гибкой WvTask, которая поддерживает полнофункциональные сопрограммы; вы можете найти его в той же библиотеке.
Работает на win32 и Linux, по крайней мере, и, возможно, в любой другой системе Unix.
Ответ 18
Я попытался сам реализовать сопрограммы с помощью С++ 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();
}
}