Сопрограммы C ++ 20, использующие final_suspend для продолжения - PullRequest
3 голосов
/ 14 марта 2020

ПРЕДПОСЫЛКИ

Убедившись, что сопрограммы C ++ без стеков довольно классные . Я реализовал сопрограммы для моей кодовой базы и понял странность в final_suspend.

CONTEXT

Допустим, у вас есть следующая final_suspend функция:

final_awaitable final_suspend() noexcept
{
    return {};
}

И, final_awaitable был реализован следующим образом:

struct final_awaitable
{
    bool await_ready() const noexcept
    {
        return false;
    }
    default_handle_t await_suspend( promise_handle_t h ) const noexcept
    { 
        return h.promise().continuation();
    }
    void await_resume() const noexcept {}
};

Если продолжение здесь было получено атомарно из очереди задач и задачи очередь может быть пустой (что может произойти в любое время между await_ready и await_suspend ), тогда await_suspend должна иметь возможность вернуть пустое продолжение.

Насколько я понимаю, когда await_suspend возвращает дескриптор, возвращенный дескриптор немедленно возобновляется (5.1 в N4775 черновик). Таким образом, если здесь не было доступного продолжения, любое приложение завершается сбоем, так как резюме вызывается на недопустимом дескрипторе сопрограммы после получения его от await_suspend .

Ниже приведен порядок выполнения:

final_suspend                        Constructs final_awaitable.
    final_awaitable::await_ready     Returns false, triggering await_suspend.
    final_awaitable::await_suspend   Returns a continuation (or empty continuation).
        continuation::resume         This could be null if a retrieved from an empty work queue.

По-видимому, не задана проверка для действительного дескриптора (как, если await_suspend возвращает bool).

ВОПРОС

  1. Как вы предполагаете добавить рабочую очередь в await_suspend без блокировки в этом случае? Ищите масштабируемое решение.
  2. Почему базовая реализация сопрограммы не проверяет действительный дескриптор.

Придуманный пример, вызывающий cra sh, здесь .

ИДЕИ РЕШЕНИЯ

  1. Использование фиктивного задания с бесконечным l oop из co_yield . Это напрасно потраченные впустую циклы, и я предпочел бы не делать этого, также мне нужно было бы создать отдельные дескрипторы для фиктивной задачи для каждого потока выполнения, и это просто кажется глупым.

  2. Создание специализации std :: coroutine_handle , где резюме ничего не делает, возвращая экземпляр этого дескриптора. Я бы предпочел не специализироваться на стандартной библиотеке. Это также не работает, потому что coroutine_handle <> не имеет done () и resume () как виртуальные.

  3. РЕДАКТИРОВАТЬ 1 16/03/2020 Вызовите продолжение () для атомарного извлечения продолжения и сохранения результата в структуре final_awaitable , await_ready мировое возвращение истина, если не было доступного продолжения. Если бы было доступно продолжение, await_ready вернуло бы false, тогда был бы вызван await_suspend , и продолжение вернулось (немедленно возобновив его). Это не работает, потому что значение, возвращаемое задачей, сохраняется в фрейме сопрограммы, и если значение все еще необходимо, фрейм сопрограммы не должен уничтожаться. В этом случае он уничтожается после вызова await_resume для final_awaitable . Это проблема, только если задача является последней в цепочке продолжений.

  4. РЕДАКТИРОВАТЬ 2 - 20/03/2020 Игнорировать возможность возврата работоспособного Дескриптор обычной процедуры от await_suspend . Возобновить продолжение только из программы высшего уровня. Это не кажется эффективным.

01/04/2020

Я до сих пор не нашел решение, которое не имеет существенные недостатки. Я полагаю, что причина, по которой я это понял, заключается в том, что await_suspend, по-видимому, предназначен для решения именно этой проблемы (возможность возврата corountine_handle). Я просто не могу понять шаблон, который был задуман.

1 Ответ

1 голос
/ 15 марта 2020

Как насчет: (Просто большой комментарий на самом деле.)

struct final_awaitable
{
    bool await_ready() const noexcept
    {
        return false;
    }
    bool await_suspend( promise_handle_t h ) const noexcept
    { 
        auto continuation = h.promise().atomically_pop_a_continuation();
        if (continuation)
           continuation.handle().resume();
        return true;//or whatever is meaningfull for your case.
    }
    void await_resume() const noexcept {}
};
...