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

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

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

Просто хотел отметить, что сопрограммы возможны в C++. Повышение - одна из возможностей. Другой - сопрограмма, утвержденная в качестве технической спецификации C++ 17. Уже есть два поддерживаемых компилятора (VC14 и Clang), и TS, скорее всего, попадет в языковой пост C++ 17. Подробности смотрите в моем ответе.

Atifm 16.08.2016 19:02

Для программистов на C, вот статья Саймона Тэтэма «Сопрограммы на C», в которой есть несколько подходов. chiark.greenend.org.uk/~sgtatham/coroutines.html одни более сложные, чем другие.

Richard Chambers 28.10.2016 18:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
65
2
49 053
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

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

В папке DOC находится pdf-файл статьи Келда Хелсгауна «Переносимая библиотека C++ для секвенирования сопрограмм», в которой описывается библиотека и приводятся короткие примеры ее использования.

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

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

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

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

См. Также Protothreads: упрощение событийного программирования встроенных систем с ограничением памяти в цифровой библиотеке ACM и обсуждение в Википедии темы Протонить,

Ответ принят как подходящий

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

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

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

Я думаю, что должен быть значок, позволяющий упомянуть работу «переполнение стека» в допустимом техническом контексте на SO!

Torsten Marek 23.09.2008 19:51

Вот хорошее стандартное решение C++, которое не требует использования Boost: akira.ruc.dk/~keld/research/COROUTINE

Dan 26.09.2008 19:59

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

Leushenko 05.02.2016 18:34

Просто примечание, хотя boost :: coroutine - отличная библиотека, сопрограммы C++ находятся на пути к тому, чтобы стать основной функцией C++ после C++ 17. В настоящее время определено в технической спецификации, а эталонные реализации находятся в Visual Studio 2015 и Clang: wg21.link/p0057r2

Atifm 22.06.2016 21:52

Сопрограммы в С ++ 20 не являются сопрограммами, которые хочет OP, потому что они не имеют стека.

Lothar 27.08.2017 08:21

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

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

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

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

Я не согласен с тем, чтобы слепо отдавать предпочтение нитям, а не волокнам. В оптимальной системе количество потоков, пытающихся запустить, равно количеству ядер (больше, если гиперпоточность). В решении с МНОЖЕСТВОМ потоков (100) волокна пула потоков могут быть гораздо более эффективными.

deft_code 15.10.2009 18:24

Спасибо за комментарий. Однако я не сказал «слепую пользу»; Я сказал «всегда считай». Я согласен с тем, что есть обстоятельства, при которых волокна или другие методологии сопрограмм могут быть более подходящими, но их может быть даже труднее «понять», чем потоки - и это о многом говорит. По сути, я предлагаю в большинстве случаев использовать потоки по умолчанию, если только вы не можете убедить себя, что есть веские причины для чего-то другого.

Euro Micelli 27.10.2009 05:25

Многопоточность означает блокировку, тогда как сопрограммы, естественно, выполняются по порядку. Бум, половина вашей работы уже сделана за вас. Потоки хороши, если вы хотите параллельно вычислять несколько тяжелых алгоритмов, я не могу придумать другой причины для их использования. Думаю, есть ли какой-нибудь блокирующий API, у которого нет неблокирующего режима?

L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ 23.11.2009 18:39

@Longpoke, я не согласен с тем, что половина вашей работы выполняется за вас с помощью сопрограмм. Вы торгуете, думая о блокировках, на разделение вашего алгоритма. Разные вещи. Иногда сопрограммы лучше, иногда нет. Вот что я имел в виду под рассмотрите возможность резьбы.

Euro Micelli 26.07.2010 04:26

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

К сожалению, makecontext() и связанные с ним функции были отмечены устаревшими в стандарте IEEE 1003.1 Posix в 2001 году (pubs.opengroup.org/onlinepubs/009695399/functions/…) и были удалены из этого стандарта в 2008 году (blog.fpmurphy.com/2009/01/ieee-std-10031-2008.html). Также известно, что в более старых реализациях pthread эти функции ломают много вещей, и, поскольку они теперь нестандартны, вряд ли кто-то будет беспокоиться о том, чтобы снова их сломать.

LiKao 14.02.2012 13:47

Сопрограммы на пути к тому, чтобы стать функцией языка C++ 17: wg21.link/p0057r2

Atifm 22.06.2016 21:51

Сопрограммы в С ++ 20 не являются сопрограммами, которые хочет OP, потому что они не имеют стека.

Lothar 27.08.2017 08:21

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

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

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

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

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

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

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

Я сам пробовал реализовать сопрограммы, используя 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();
    }
}

Разве это не просто реализация производителя-потребителя?

André Caron 14.08.2012 21:00

Ты потерял меня, когда сказал нить. Сопрограммам не нужны потоки.

Atifm 22.06.2016 21:50

Да, это было давно, когда я толком не понимал, что такое сопрограммы;)

jhasse 29.06.2016 15:54

Сегодня была выпущена новая библиотека Boost.Context с переносимыми функциями для реализации сопрограмм.

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

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

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

Эта функция теперь включена в техническую спецификацию, намеченную для публикации C++ 17: wg21.link/p0057r2

Atifm 22.06.2016 21:49

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

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

C-сопрограммы, использующие устройство Даффа

И в качестве примера вот библиотека telnet, которую я модифицировал, чтобы использовать сопрограммы вместо вилок / потоков: Библиотека telnet cli с использованием сопрограмм

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

Для тех, кто хочет знать, как они могут переносить сопрограммы на 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 ключевые слова будут возобновлены и await.

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

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

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

Обновлять: Похоже, реализация сопрограммы намечена для C++ 20, но была выпущена в виде технической спецификации с C++ 17 (p0057r2). Visual C++, clang и gcc позволяют выбрать использование флага времени компиляции.

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

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

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

Я тестировал его на armv5, armv6, armv7 и arm64. Если не получится, исправь, патч приму. Обратите внимание, что вам нужен STL.

user1095108 23.06.2018 18:37

Также на основе макросов (устройство Даффа, полностью портативное, см. 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;
}

Другие вопросы по теме