Как вы реализуете 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. Это хорошая отправная точка для изучения реализации сопрограммы в С++.

Ответ 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();
    }
}