Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does it mean for "With a stackless coroutine, only the top-level routine may be suspended."

I found that statement from here. At first I was astonished because I believe that makes stackless coroutines almost useless (And C++ coroutine TS is stackless). So I wrote a demo (In visual studio using C++ coroutine TS):

#include<experimental/coroutine>
#include<iostream>
#include<thread>
#include<mutex>
#include<future>
#include<chrono>

using namespace std;
using namespace std::chrono;
using namespace std::experimental;

class AsyncQueue {
public:
    class Awaitable {
        friend AsyncQueue;
        AsyncQueue& mQueue;
        coroutine_handle<> mCoroutineHandle;
        Awaitable* mNext = nullptr;
    public:
        Awaitable(AsyncQueue& queue):mQueue(queue){}

        bool await_ready() const noexcept {
            return false;
        }

        bool await_suspend(coroutine_handle<> coroutineHandle) noexcept
        {
            mCoroutineHandle = coroutineHandle;
            mQueue.enqueue(this);
            return true;
        }

        void await_resume() noexcept {}
    };
private:
    mutex mMutex;
    Awaitable* mHead = nullptr;
    Awaitable* mTail = nullptr;
    void enqueue(Awaitable* awaitable){
        lock_guard<mutex> g{ mMutex };
        if (mTail) {
            mTail->mNext = awaitable;
            mTail = awaitable;
        }
        else {
            mTail = awaitable;
            mHead = mTail;
        }
    }

    Awaitable* dequeue() {
        lock_guard<mutex> g{ mMutex };
        Awaitable* result = mHead;
        mHead = nullptr;
        mTail = nullptr;
        return result;
    }

public:
    Awaitable operator co_await() noexcept {
        return Awaitable{ *this };
    }

    bool poll() {
        Awaitable* awaitables = dequeue();
        if (!awaitables) {
            return false;
        }
        else {
            while (awaitables) {
                awaitables->mCoroutineHandle.resume();
                awaitables = awaitables->mNext;
            }
            return true;
        }
    }
};


AsyncQueue toBackgroundThread;
AsyncQueue toMainThread;

std::future<void> secondLevel(int id)
{
    co_await toBackgroundThread;
    cout << id << " run on " << this_thread::get_id() << endl;
    co_await toMainThread;
    cout << id << " run on " << this_thread::get_id() << endl;
}

std::future<void> topLevel() {
    co_await secondLevel(1);
    co_await secondLevel(2);
}

void listen(AsyncQueue& queue) {
    while (true) {
        if (!queue.poll()) {
            this_thread::sleep_for(100ms);
        }
    }
}

int main() {
    thread([]() {
        listen(toBackgroundThread);
    }).detach();

    topLevel();

    listen(toMainThread);
}

coroutine topLevel calls two secondLevel (which I believe are suspendable non-top-level routines), and it works fine. The code above prints:

1 run on 16648
1 run on 3448
2 run on 16648
2 run on 3448

From that answer it is claimed that This prohibits providing suspend/resume operations in routines within a general-purpose library. I see no prohibitions here.

like image 909
W.H Avatar asked Oct 22 '18 07:10

W.H


People also ask

What is stackless coroutine?

A coroutine is a function that can suspend execution to be resumed later. Coroutines are stackless: they suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack.

What are the characteristics of co routine feature?

Characteristics of a coroutine are: values of local data persist between successive calls (context switches) execution is suspended as control leaves coroutine and is resumed at certain time later. symmetric or asymmetric control-transfer mechanism; see below.

How does boost coroutine work?

The Boost coroutine library allows us to specify the “push_type” where this functions should be suspended, after reaching this point, a subsequent call to “yield()” is required to resume this function. Up until “yield()”, the function logs the first line to stdout.

What is C++ coroutine?

A coroutine is a special function that can suspend its execution and resume later at the exact point where execution was suspended. When suspending, the function may return (yield) a value.


1 Answers

In each invocation of co_await, only the top level coroutine is suspended. To suspend a lower level, that level must suspend itself explicitly. And at that point, it is now the current "top level". So in every case, only the current top level gets suspended.

Compare this to a purely hypothetical stackful coroutine library:

//This function will always print the same thread ID.
void secondLevel(int id)
{
    while(!toBackgroundThread.poll())
      suspend_coroutine();

    cout << id << " run on " << this_thread::get_id() << endl;

    while(!toBackgroundThread.poll())
      suspend_coroutine();

    cout << id << " run on " << this_thread::get_id() << endl;
}

void topLevel() {
    secondLevel(1);
    secondLevel(2);
}

void listen(AsyncQueue& queue) {
    while (true) {
        if (!queue.poll()) {
            this_thread::sleep_for(100ms);
        }
    }
}

int main() {
    thread([]() {
        listen(toBackgroundThread);
    }).detach();

    auto coro = create_coroutine(topLevel);
    coro.switch_to();

    toMainThread.ready(); //Notes that the main thread is waiting
    while (true) {
        if (!toMainThread.poll()) {
            coro.switch_to();
        }
    }
};

topLevel doesn't have any explicit suspension machinery. Yet its execution suspends whenever any function it calls suspends execution. The entire call-stack, defined by the function given to create_coroutine and everything it calls, suspends. That's how a stackful coroutine works.

That is what is being contrasted with when it comes to stackless coroutines. In the stackless version, every function that needs to suspend must be specifically coded to do so. And thus isn't really "general purpose" anymore; it's now special-cased to suspending scenarios.

like image 113
Nicol Bolas Avatar answered Oct 25 '22 02:10

Nicol Bolas