你如何在C ++中实现Coroutines

时间:2008-09-23 15:31:03

标签: c++ coroutine c++17

我怀疑它可以移植,但有没有解决方案?我认为可以通过创建备用堆栈并在功能输入上重置SP,BP和IP,并使产量保存IP并恢复SP + BP来完成。析构函数和异常安全似乎很棘手,但可以解决。

它已经完成了吗?这不可能吗?

18 个答案:

答案 0 :(得分:87)

是的可以完成没有问题。您只需要一个小的汇编代码来将调用堆栈移动到堆上新分配的堆栈。

我会查看boost::coroutine

你应该注意的一件事是堆栈溢出。在大多数操作系统上,溢出堆栈将导致段错误,因为未映射虚拟内存页。但是,如果您在堆上分配堆栈,则无法获得任何保证。 请记住这一点。

答案 1 :(得分:18)

在POSIX上,您可以使用makecontext()/ swapcontext()例程来可移植地切换执行上下文。在Windows上,您可以使用光纤API。否则,您只需要一些胶水装配代码来切换机器上下文。我已经使用ASM(对于AMD64)和swapcontext()实现了协同程序;既不是很难。

答案 2 :(得分:15)

对后代来说,

Dmitry Vyukov的wondeful web site有一个聪明的技巧,使用ucontext和setjump来模拟c ++中的协同程序。

此外,Oliver Kowalke的上下文库是recently accepted进入Boost,所以希望我们能够看到更快版本的boost.coroutine,它很快就会在x86_64上运行。

答案 3 :(得分:10)

没有简单的方法来实现协同程序。由于协程本身不像C / C ++的堆栈抽象,就像线程一样。因此,如果没有语言级别的更改,就无法支持它。

目前(C ++ 11),所有现有的C ++协程实现都基于程序集级别的黑客攻击,难以跨越平台安全可靠。为了可靠,它需要是标准的,并由编译器处理而不是黑客攻击。

这有一个standard proposal - N3708。如果您有兴趣,请查看它。

答案 4 :(得分:7)

如果可能的话,使用迭代器可能比使用协程更好。这样你可以继续调用next()来获取下一个值,但是你可以将你的状态保存为成员变量而不是局部变量。

它可能使事情更易于维护。另一个C ++开发人员可能不会立即理解协程,而他们可能更熟悉迭代器。

答案 5 :(得分:6)

对于那些想要了解他们如何在C ++中以便携方式利用Coroutines的人来说,等待已经结束了(见下文),这些人可以在C ++中以便携式方式使用Coroutines!标准委员会正在研究该功能,请参阅N3722 paper。总结本文的当前草案,而不是Async和Await,关键字将是可恢复的,等待。

看一下Visual Studio 2015中的实验性实现,以便与Microsoft的实验性实现一起使用。看起来clang还没有实现。

Cppcon Coroutines a negative overhead abstraction有一个很好的演讲,概述了在C ++中使用Coroutines的好处,以及它如何影响代码的简单性和性能。

目前我们仍然需要使用库实现,但在不久的将来,我们将使用协同程序作为核心C ++特性。

更新: 看起来协程实现是针对C ++ 20的,但是作为C ++ 17(p0057r2)的技术规范发布。 Visual C ++,clang和gcc允许您选择使用编译时标志。

答案 6 :(得分:5)

我不认为C ++中有许多完整的,干净的实现。我喜欢的一次尝试是Adam Dunkels' protothread library

另见ACM数字图书馆中的Protothreads: simplifying event-driven programming of memory-constrained embedded systems和维基百科主题Protothread中的讨论,

答案 7 :(得分:5)

COROUTINE a portable C++ library for coroutine sequencing是否指向正确的方向?它似乎是一个优雅的解决方案,经历了时间的考验.....它已经9岁了!

在DOC文件夹中是Keld Helsgaun撰写的用于协同序列的便携式C ++库的pdf文件,其中描述了该库并提供了使用它的简短示例。

[更新]我实际上是自己成功使用它。好奇心让我变得更好,所以我调查了这个解决方案,发现它非常适合我已经工作了一段时间的问题!

答案 8 :(得分:3)

今天发布了一个新的库 Boost.Context ,其中包含用于实现协同程序的便携式功能。

答案 9 :(得分:3)

这是一个老线程,但我想建议一个使用Duff的设备的黑客攻击(据我记得):

C coroutines using Duff's device

作为一个例子,这里是一个telnet库我修改为使用协程而不是fork / threads: Telnet cli library using coroutines

由于C99之前的标准C本质上是C ++的真正子集,因此在C ++中也能很好地工作。

答案 10 :(得分:2)

它基于(cringe)宏,但以下站点提供了易于使用的生成器实现:http://www.codeproject.com/KB/cpp/cpp_generators.aspx

答案 11 :(得分:2)

我已经提出了一个实施没有asm 代码。我们的想法是使用系统的线程创建函数来初始化堆栈和上下文,并使用setjmp / longjmp来切换上下文。但它不便携,如果您有兴趣,请参阅tricky pthread version

答案 12 :(得分:1)

也基于宏(达夫设备,完全可移植,请参见  http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html ),并受到Mark发布的链接的启发,以下内容以事件为同步机制(与传统的协同例程/生成器样式稍有不同的模型)来模拟协同流程。

// 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;
}

答案 13 :(得分:1)

查看我的实现,它说明了asm黑客攻击点并且很简单:

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

答案 14 :(得分:1)

https://github.com/tonbit/coroutine是C ++ 11单.h非对称协程实现,支持resume / yield / await primitives和Channel model。它是通过ucontext / fiber实现的,不依赖于boost,在linux / windows / macOS上运行。这是学习在c ++中实现协同程序的一个很好的起点。

答案 15 :(得分:0)

WvContWvStreams的一部分,它实现了所谓的半协同程序。这些比完全协同程序更容易处理:你调用它,然后它会回复给那个调用它的人。

它使用更灵活的WvTask实现,它支持全开程协同程序;你可以在同一个图书馆找到它。

适用于win32和Linux,至少适用于任何其他Unix系统。

答案 16 :(得分:0)

你应该总是考虑使用线程;特别是在现代硬件中。如果你的工作可以在协同例程中进行逻辑分离,那么使用线程意味着工作实际上可以由不同的执行单元(处理器核心)同时完成。

但是,也许你确实想要使用协同程序,也许是因为你有一个经过良好测试的算法,这个算法已经以这种方式编写和测试,或者因为你正在移植以这种方式编写的代码。

如果您在Windows中工作,则应该查看fibers。纤维将在操作系统的支持下为您提供类似协程的框架。

我不熟悉其他操作系统,以推荐替代品。

答案 17 :(得分:-2)

我尝试使用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();
    }
}