73

我怀疑它可以便携地完成,但有什么解决方案吗?我认为可以通过创建一个备用堆栈并在函数入口处重置 SP、BP 和 IP,并让产量保存 IP 并恢复 SP+BP 来完成。析构函数和异常安全似乎很棘手,但可以解决。

已经完成了吗?这是不可能的吗?

4

19 回答 19

97

是的,它可以毫无问题地完成。您只需要一点汇编代码就可以将调用堆栈移动到堆上新分配的堆栈。

我会看看boost::coroutine library

您应该注意的一件事是堆栈溢出。在大多数操作系统上,堆栈溢出将导致段错误,因为未映射虚拟内存页面。但是,如果您在堆上分配堆栈,您将得不到任何保证。要时刻铭记在心。

于 2008-09-23T15:38:04.503 回答
18

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

于 2008-09-23T16:05:46.980 回答
15

为了后代,

Dmitry Vyukov 的精彩网站有一个巧妙的技巧,使用 ucontext 和 setjump 来模拟 c++ 中的协程。

此外,Oliver Kowalke 的上下文库最近被Boost 接受,所以希望我们很快就会看到在 x86_64 上工作的 boost.coroutine 的更新版本。

于 2012-02-24T22:22:41.603 回答
11

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

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

有一个标准提案 - N3708。如果您有兴趣,请查看它。

于 2013-10-26T06:13:10.227 回答
7

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

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

于 2008-09-23T15:33:45.933 回答
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 中的实验性实现来玩微软的实验性实现。看起来clang还没有实现。

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

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

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

于 2016-02-19T15:13:44.373 回答
5

COROUTINE一个用于协程排序的可移植 C++ 库是否为您指明了正确的方向?这似乎是一个经过时间考验的优雅解决方案......它已经 9 岁了!

在 DOC 文件夹中是 Keld Helsgaun 的论文 A Portable C++ Library for Coroutine Sequencing 的 pdf,它描述了该库并提供了使用它的简短示例。

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

于 2008-09-23T15:33:10.693 回答
5

我认为 C++ 中没有很多成熟、干净的实现。我喜欢的一种尝试是Adam Dunkels 的 protothread library

另请参阅Protothreads:在 ACM 数字图书馆中简化内存受限嵌入式系统的事件驱动编程和维基百科主题Protothread中的讨论,

于 2008-09-23T15:37:26.220 回答
3

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

于 2010-11-04T13:31:09.460 回答
3

今天发布了一个新库Boost.Context,它具有用于实现协程的可移植特性。

于 2012-08-21T15:34:33.323 回答
3

这是一个旧线程,但我想建议使用不依赖于操作系统的 Duff 设备进行黑客攻击(据我所知):

使用 Duff 设备的 C 协程

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

由于 C99 之前的标准 C 本质上是 C++ 的真正子集,因此这在 C++ 中也很有效。

于 2015-12-04T15:48:31.273 回答
2

我想出了一个没有 asm代码的实现。思路是使用系统的线程创建函数初始化栈和上下文,使用setjmp/longjmp切换上下文。但它不是可移植的,如果您有兴趣,请参阅棘手的 pthread 版本。

于 2014-08-12T17:34:45.657 回答
1

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

于 2016-11-16T01:58:34.480 回答
1

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

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

于 2017-01-05T08:29:42.167 回答
1

也基于宏(Duff 的设备,完全可移植,参见 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;
}
于 2019-10-07T16:12:54.597 回答
0

WvCont是 WvStreams 的一部分,实现了所谓的半协程。这些比完整的协程更容易处理:你调用它,它会返回给调用它的人。

它是使用更灵活的 WvTask 实现的,它支持完整的协程;你可以在同一个库中找到它。

至少可以在 win32 和 Linux 上工作,并且可能在任何其他 Unix 系统上工作。

于 2008-09-23T19:28:55.010 回答
-1

您应该始终考虑使用线程;尤其是在现代硬件中。如果您的工作可以在协程中进行逻辑分离,那么使用线程意味着工作实际上可能由单独的执行单元(处理器内核)同时完成。

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

如果你在 Windows 中工作,你应该看看fiber。Fibers 将在操作系统的支持下为您提供类似协程的框架。

我不熟悉其他操作系统来推荐那里的替代品。

于 2008-09-23T15:58:51.223 回答
-1

这是一个 C++11 中的跨平台 go 风格的协程库:cocoyaxi

它提供了一个很好的 API,你可以像在 golang 中那样创建协程:

go(ku);           // void ku();
go(f, 7);         // void f(int);
go(&T::f, &o);    // void T::f(); T o;
go(&T::f, &o, 7); // void T::f(int); T o;
go([](){
    LOG << "hello go";
});

cocoyaxi 也实现了channel,waitgroupdefer在 golang 中。

于 2020-01-16T06:54:39.310 回答
-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();
    }
}
于 2012-05-27T18:10:55.940 回答