我怀疑它可以移植,但有没有解决方案?我认为可以通过创建备用堆栈并在功能输入上重置SP,BP和IP,并使产量保存IP并恢复SP + BP来完成。析构函数和异常安全似乎很棘手,但可以解决。
它已经完成了吗?这不可能吗?
答案 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)
WvCont是WvStreams的一部分,它实现了所谓的半协同程序。这些比完全协同程序更容易处理:你调用它,然后它会回复给那个调用它的人。
它使用更灵活的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();
}
}