Что это означает для «сопрограммы без стеков, только подпрограмма верхнего уровня может быть приостановлена». - PullRequest
0 голосов
/ 22 октября 2018

Я нашел это утверждение из здесь .Сначала я был удивлен, потому что я считаю, что это делает сопрограммы без стеков почти бесполезными (а C + сопрограммы TS без стеков).Поэтому я написал демо (в Visual Studio, использующей сопрограмму C ++ 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);
}

сопрограмма topLevel вызывает два secondLevel (которые, я считаю, являются неподходящими подпрограммами не верхнего уровня), и это работаетхорошо.Код выше печатает:

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

Из этого ответа утверждается, что This prohibits providing suspend/resume operations in routines within a general-purpose library. Я не вижу здесь никаких запретов.

1 Ответ

0 голосов
/ 22 октября 2018

При каждом вызове co_await приостанавливается только сопрограмма верхнего уровня.Чтобы приостановить более низкий уровень, этот уровень должен приостановить себя явно.И на тот момент, это сейчас текущий «верхний уровень».Поэтому в каждом случае приостанавливается только текущий верхний уровень.

Сравните это с чисто гипотетической библиотекой стековых сопрограмм:

//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 не имеет явного механизма приостановки.И все же его выполнение приостанавливается всякий раз, когда любая вызываемая им функция приостанавливает выполнение.Весь стек вызовов, определенный функцией, данной create_coroutine, и все, что он вызывает, приостанавливается.Вот как работает стековая сопрограмма.

Это то, с чем противопоставляется, когда речь идет о сопрограммах без стека.В версии без стека каждая функция, которая должна быть приостановлена, должна быть специально закодирована , чтобы сделать это.И, таким образом, больше не является «общим назначением»;теперь он специально предназначен для сценариев приостановки.

...