Что такое stackful coroutines вполне понятно — в памяти создается дополнительный стек, на который переключается регистр-указатель стека процессора. И в нем можно все то же самое что и в основном — вызывать функции, возвращать значения и т.д.
А вот что такое stackless coroutines? Судя по названию — "безстековые", но тогда как они работают? В чем разница со stackful? Как они реализуются на низком уровне? Каковы преимущества и недостатки обоих подходов?
Здравствуйте, x-code, Вы писали:
Stackless coroutines преобразуют код в state machine.
Для С++ это выглядит примерно так:
допустим, у нас есть простой класс task<T> (aka Promise aka future)
template<typename T>
class task {
public:
// wait and return task result
T get();
// pass task result to |continuation|
void then(function<void(T)> continuation);
};
template<typename T>
class task_completion_event {
public:
void set(T t); // set task result
};
template<typename T> task<T> create_task(task_completion_event<T> completion);
Мы пишем следующий асинхронный код
task<vector<int>> g();
task<int> h(int x);
task<int> f(int x0) async {
vector<int> v = await g();
int sum = x0;
for (int i = 0; i != v.size(); ++i) {
sum += await h(v[i]);
}
return sum;
}
int main() {
task<int> t = f(100);
return t.get();
}
и компилятор переписывает f() в следующую функцию:
task<int> f(int x0) {
struct Context {
int x0;
vector<int> v;
int sum;
int i;
int x;
task_completion_event<int> result;
int state = 0;
static void next(shared_ptr<Context> ctx) {
for (;;) {
switch (ctx->state) {
case 0:
g().then([ctx](vector<int> v){
ctx->v = v;
next(ctx);
});
ctx->state = 1;
return;
case 1:
ctx->sum = ctx->x0;
ctx->i = 0;
ctx->state = 2;
break;
case 2:
if (ctx->i != ctx->v.size())
ctx->state = 3;
else
ctx->state = 5;
break;
case 3:
h(ctx->v[ctx->i]).then([ctx](int x){
ctx->x = x;
next(ctx);
});
ctx->state = 4;
return;
case 4:
ctx->sum += ctx->x;
++ctx->i;
ctx->state = 2;
break;
case 5:
ctx->result.set(ctx->sum);
return;
}
}
};
shared_ptr<Context> ctx = make_shared<Context>();
ctx->x0 = x0;
task<int> t = create_task(ctx->result);
Context::next(ctx);
return t;
}