картина

[Примечание редактора CSDN] Два года назад был официально выпущен C++20. В этой версии разработчики, наконец, представили функцию сопрограммы, которая может сделать код очень чистым, простым и понятным, сохраняя при этом высокую производительность асинхронности. Однако многие разработчики прямо заявили, что разработчики библиотек используют стандарт сопрограмм C++, что очень сложно и совсем не дружелюбно для обычных разработчиков. В этой статье, основанной на стандарте бесстековых сопрограмм, используемом C++20, Ци Ю, старший технический эксперт по C++, делится конкретной практикой применения и опытом использования сопрограмм с конкретными примерами.


Автор | Ци Юй, Сюй Чуанци, редактор Хань Яо       | Ту Минь
Произведено | CSDN (ID: CSDNnews)

После многих лет раздумий, дебатов и подготовки сопрограммы наконец вошли в стандарт C++20.

картина


картина

Бесстековая сопрограмма , предложенная и возглавляемая Microsoft, стала стандартом сопрограмм C++20.


Сопрограммы — не новая концепция, они существуют уже несколько десятилетий и существуют во многих других языках программирования (Python, C#, Go).

Сопрограммы делятся на два типа: сопрограммы без стеков и сопрограммы со стеком.Сопрограммы без стеков относятся к функциям, которые можно приостановить/возобновить, а сопрограммы со стеками эквивалентны потокам пользовательского режима. Стоимость переключения между стекируемыми сопрограммами — это стоимость переключения потоков в пользовательском режиме, а стоимость переключения между бесстековыми сопрограммами эквивалентна стоимости вызовов функций.

Разница между сопрограммами без стека и потоками: сопрограммы без стека могут вызываться только потоками, и они не вытесняют планирование ядра, в то время как потоки могут вытеснять планирование ядра.

Сопрограмма C++20 использует бесстековую сопрограмму, предложенную и доминирующую Microsoft (производную от C#). Многие люди выступают против этой функции, к основным недостаткам относятся: сложность для понимания, слишком гибкая, проблемы с производительностью, вызванные динамическим размещением и т. д. Google запустил серию жалоб на это предложение и попытался предложить решение с сопрограммами стека. Сопрограммы с стеком намного легче, чем потоки системного уровня, но все же намного хуже, чем сопрограммы без стеков.

Поскольку философия разработки C++ заключается в « абстракциях с нулевыми накладными расходами », бесстековые сопрограммы в конечном итоге стали стандартом сопрограмм C++20.

Две основные темы эволюции мира C++ сегодня — это асинхронность и параллелизм . Сопрограмма C++20 может писать асинхронный код в синхронном синтаксисе, что делает ее хорошим инструментом для написания асинхронного кода.Сопрограммизация асинхронных библиотек станет общей тенденцией, поэтому необходимо освоить сопрограммы C++20.

Покажем «красоту» сопрограмм на простом примере.

async_resolve({host, port}, [](auto endpoint){
  async_connect(endpoint, [](auto error_code){
    async_handle_shake([](auto error_code){
        send_data_ = build_request();

        async_write(send_data_, [](auto error_code){
            async_read();
        });
    });
    });
});

void async_read() {
    async_read(response_, [](auto error_code){
        if(!finished()) {
            append_response(recieve_data_);
            async_read();
        }else {
            std::cout<<"finished ok\n";
        }
    });
}

Псевдокод для асинхронного клиента на основе обратного вызова

Клиентский процесс, основанный на асинхронном обратном вызове, выглядит следующим образом:

  • Асинхронное разрешение доменного имени

  • Асинхронное соединение

  • Асинхронное рукопожатие SSL

  • Отправка данных асинхронно

  • Получать данные асинхронно

В этом коде много функций обратного вызова, и есть некоторые ловушки при использовании обратных вызовов, например, как обеспечить безопасные обратные вызовы, как заставить асинхронное чтение реализовать асинхронные рекурсивные вызовы, в сочетании с асинхронной бизнес-логикой уровень вложенности обратных вызовов будет глубже, мы видели В тени обратного ада! Некоторым читателям может показаться, что такой уровень асинхронного обратного вызова приемлем, но если проект становится больше, бизнес-логика усложняется, а уровень обратного вызова становится все глубже и глубже, его будет сложно поддерживать.

Давайте посмотрим, как написать этот код с помощью сопрограмм:

auto endpoint = co_await async_query({host, port});
auto error_code = co_await async_connect(endpoint);
error_code = co_await async_handle_shake();
send_data = build_request();
error_code = co_await async_write(send_data);
while(true) {
    co_await async_read(response);
    if(finished()) {
        std::cout<<"finished ok\n";
        break;
    }

    append_response(recieve_data_);
}

Асинхронный клиент на основе сопрограммы C++20

Это также асинхронный клиент.По сравнению с асинхронным клиентом в режиме обратного вызова, весь код очень свежий, простой и понятный, при этом сохраняется высокая производительность асинхронного.В этом сила сопрограмм C++20!

Я думаю, что после прочтения этого примера вы больше не захотите писать код с асинхронными обратными вызовами, пришло время использовать сопрограммы!


картина

Почему C++20 выбирает бесстековые сопрограммы?


Обычная реализация стековых сопрограмм заключается в выделении большого пространства памяти (например, 64 КБ) в куче заранее, что является так называемым «стеком» сопрограмм. В этом «стеке» могут храниться параметры, адреса возврата и т. д. ". "В космосе. Если требуется переключение сопрограммы, систему можно заставить думать, что пространство в куче — это обычный стек с помощью swapcontext, реализующего переключение контекста.

Самым большим преимуществом сопрограммы стека является то, что она менее навязчива и очень проста в использовании.Существующий бизнес-код вряд ли нуждается в изменении, но C++20, наконец, решил использовать сопрограмму без стека, в основном из-за следующих аспектов рассмотрения .

  • ограничение места в стеке

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

  • производительность

Стекированные сопрограммы действительно легче, чем системные потоки при переключении, но они все же тяжелее, чем нестекированные сопрограммы, хотя это мало влияет на наше текущее фактическое использование (использование асинхронных систем обычно сопровождается вводом-выводом, который составляет несколько порядков величины). дороже, чем переключение), но это также определяет, что бесстековые сопрограммы можно использовать в некоторых более интересных сценариях. Например, Гор Нишанов, автор предложения сопрограмм C++20, продемонстрировал на CppCon 2018, что бесстековые сопрограммы могут переключаться за наносекунды, и на основе этой функции реализована функция уменьшения промахов кеша.

Бесстековая сопрограмма — это обобщение обычной функции.

Бесстековая сопрограмма — это функция, которую можно приостанавливать и возобновлять, обобщение вызовов функций.

Почему?

Мы знаем, что тело функции выполняется последовательно.После завершения выполнения результат возвращается вызывающей стороне.Нет возможности приостановить его и возобновить позже,но дождаться его завершения. Бесстековая сопрограмма позволяет нам приостановить функцию, а затем возобновить и выполнить тело функции в любое время.По сравнению с обычными функциями, тело функции сопрограммы можно приостановить и возобновить выполнение в любое время.

картина

Итак, с этой точки зрения бесстековые сопрограммы — это обобщения обычных функций.


картина

C++20 Корутина "Маленькие слова"


C++20 предоставляет три новых ключевых слова (co_await, co_yield и co_return), если одно из этих трех ключевых слов существует в функции, это сопрограмма.

Компилятор генерирует много кода для сопрограмм для реализации семантики сопрограмм. Какой код будет сгенерирован? Как реализовать семантику сопрограмм? Как создается сопрограмма? Что такое механизм co_await? Прежде чем приступить к изучению этих вопросов, давайте рассмотрим некоторые основные понятия, связанные с сопрограммами C++20.

Объекты, связанные с сопрограммой

кадр сопрограммы

Когда вызывающая сторона вызывает сопрограмму, она сначала создает фрейм сопрограммы, фрейм сопрограммы создает объект-обещание, а затем генерирует возвращаемый объект через объект-обещание.

Основное содержимое фрейма сопрограммы следующее:

  • Параметры сопрограммы

  • локальная переменная

  • объект обещания

Это содержимое необходимо, когда сопрограмма возобновляет работу.Вызывающий получает доступ к фрейму сопрограммы через дескриптор std::coroutine_handle фрейма сопрограммы.

обещание_тип

promise_type — это тип объекта обещания. Promise_type используется для определения поведения класса сопрограмм, включая то, как создается сопрограмма, поведение при инициализации и завершении сопрограммы, поведение при возникновении исключения, поведение при генерации ожидающего и поведение co_return и т. д. Объекты Promise можно использовать для записи/сохранения состояния экземпляра сопрограммы. Каждый кадр сопрограммы имеет однозначное соответствие с каждым объектом обещания и каждым экземпляром сопрограммы.

сопрограмма возвращает объект

Он создается методом promise.get_return_object().Общий метод реализации будет хранить coroutine_handle в объекте сопрограммы, чтобы возвращаемый объект имел возможность доступа к сопрограмме.

std::coroutine_handle

Дескриптор фрейма сопрограммы, который в основном используется для доступа к базовому фрейму сопрограммы, восстановления фрейма сопрограммы и освобождения фрейма сопрограммы.
Программисты могут разбудить сопрограммы, вызвав std::coroutine_handle::resume().

co_await, ожидающий, ожидаемый

  • co_await: унарный оператор;

  • awaitable: типы, поддерживающие оператор co_await;

  • awaiter: определяет типы методов await_ready, await_suspend и await_resume.

co_await expr обычно используется для указания на ожидание завершения задачи (которая может быть или не быть ленивой). Когда co_await expr, тип expr должен быть ожидаемым, а конкретная семантика выражения co_await зависит от ожидающего, сгенерированного в соответствии с ожидаемым.

Кажется, что есть еще много объектов, связанных с сопрограммами, здесь сопрограммы сложные и гибкие, с помощью этих объектов можно добиться полного контроля над сопрограммами и реализовать любые идеи. Однако сначала вам нужно понять, как взаимодействуют эти объекты.Если вы это поймете, вы освоите принцип сопрограмм и сможете с легкостью писать сопрограммы-приложения.

Как объекты сопрограммы работают вместе

Простой код, показывающий, как эти объекты сопрограммы работают вместе:

Return_t foo () { 
    auto res = co_await awaiter; 
    co_return res ; 
}

Return_t: объект возврата обещания.

awaiter: Ожидание завершения задачи.

картина

Блок-схема работы корутины

Метод в светло-синей части рисунка — это функция объекта обещания, связанного с Return_t, а светло-красная часть — это ожидающий, которого ожидает co_await.

Движение этого процесса управляется кодом, сгенерированным компилятором в соответствии с функцией сопрограммы, которая разделена на три части:

  • создание корутины;

  • co_await awaiter ожидает завершения задачи;

  • Получите возвращаемое значение сопрограммы и освободите кадр сопрограммы.

Создание сопрограмм

Return_t foo () { 
    auto res = co_await awaiter; 
    co_return res ; 
}

Сопрограмма foo() сгенерирует следующий код шаблона (псевдокод), а создание сопрограммы сгенерирует аналогичный код:

{
  co_await promise.initial_suspend();
  try
  {
    coroutine body;
  }
  catch (...)
  {
    promise.unhandled_exception();
  }
FinalSuspend:
  co_await promise.final_suspend();
}

Во-первых, необходимо создать сопрограмму, а приостановка работы после создания сопрограммы определяется вызывающей стороной, устанавливающей тип возвращаемого значения initial_suspend.

Процесс создания сопрограммы примерно такой:

  • Создание корутинового фрейма

  • Построение объектов промисов в фреймах сопрограмм

  • Скопируйте параметры сопрограммы в фрейм сопрограммы

  • Вызовите promise.get_return_object(), чтобы вернуть вызывающей стороне объект, который является объектом Return_t в коде.

В этой структуре шаблона есть несколько настраиваемых точек: например, initial_suspend, final_suspend, unhandled_exception и return_value.

Мы можем контролировать, будет ли сопрограмма приостановлена ​​с помощью возвращаемых типов промисов initial_suspend и final_suspend, обрабатывать исключения в unhandled_exception и сохранять возвращаемое значение сопрограммы в return_value.

Вы можете настроить возвращаемые объекты initial_suspend и final_suspend по мере необходимости, чтобы решить, приостанавливать ли сопрограмму. Если сопрограмма приостановлена, управление кодом возвращается вызывающей стороне, в противном случае выполнение продолжается с телом функции сопрограммы.

картина

Также стоит отметить, что если вы отключите исключения, в сгенерированном коде не будет try-catch. В настоящее время эффективность работы сопрограммы почти такая же, как у версии обычной функции без сопрограммы. Это важно во встроенных сценариях и является одной из целей разработки сопрограмм.

механизм co_await

Оператор co_await — это новое ключевое слово в C++ 20. Co_await expr обычно означает ожидание отложенной задачи вычисления. Эта задача может выполняться в потоке или в ядре ОС. Время окончания выполнения неизвестно. Для повышения производительности мы не хочу блокировать и ждать завершения задачи, поэтому мы используем co_await, чтобы приостановить сопрограмму и вернуть ее вызывающему абоненту.Вызывающий может продолжать делать что-то.Когда задача завершена, сопрограмма возобновляет работу и получает результат возвращено co_await.

Таким образом, co_await обычно имеет следующие функции:

  • приостановить сопрограмму;

  • вернуться к звонящему;

  • Возвращает результат задачи после ожидания завершения задачи (которая может быть ленивой или неленивой).

Компилятор сгенерирует такой код в соответствии с выражением co_await:

{
  auto&& value = <expr>;
  auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value));
  auto&& awaiter = get_awaiter(static_cast<decltype(awaitable)>(awaitable));
  if (!awaiter.await_ready()) //是否需要挂起协程
  {
    using handle_t = std::experimental::coroutine_handle<P>;

    using await_suspend_result_t =
      decltype(awaiter.await_suspend(handle_t::from_promise(p)));

    <suspend-coroutine> //挂起协程

    if constexpr (std::is_void_v<await_suspend_result_t>)
    
{
      awaiter.await_suspend(handle_t::from_promise(p)); //异步(也可能同步)执行task
      <return-to-caller-or-resumer> //返回给caller
    }
    else
    {
      static_assert(
         std::is_same_v<await_suspend_result_tbool>,
         "await_suspend() must return 'void' or 'bool'.");

      if (awaiter.await_suspend(handle_t::from_promise(p)))
      {
        <return-to-caller-or-resumer>
      }
    }

    <resume-point> //task执行完成,恢复协程,这里是协程恢复执行的地方
  }

  return awaiter.await_resume(); //返回task结果
}

Этот процесс выполнения кода является розовой частью "блок-схемы выполнения сопрограммы". Как видно из этого сгенерированного кода, вы можете контролировать, следует ли приостановить сопрограмму или продолжить выполнение, настроив возвращаемое значение awaiter.await_ready(), и return false Сопрограмма будет приостановлена, и awaiter.await_suspend будет выполнен, а возвращаемое значение awaiter.await_suspend определит, следует ли вернуться к вызывающей стороне или продолжить выполнение.

Именно этот механизм co_await является ключом к изменению «асинхронного обратного вызова» на «синхронный».

Двумя наиболее важными объектами в сопрограмме C++20 являются объект обещания (восстановление сопрограммы и получение результата выполнения задачи) и объект ожидания (приостановка сопрограммы и ожидание завершения задачи), остальные являются «инструментами». », чтобы получить желаемую сопрограмму, ключ в том, чтобы спроектировать, как заставить эти два объекта хорошо взаимодействовать.

Для получения более подробной информации о co_await читатели могут обратиться к этому документу ( https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await ).

Маленькие слова и праведность

Вернемся и посмотрим на эту простую сопрограмму:

Return_t foo () { 
    auto res = co_await awaiter; 
    co_return res ; 
}

Сопрограмма foo имеет всего три строки кода, но в итоге генерирует более 100 строк кода. Например, создание сопрограммы и механизм co_await реализуются этими кодами».

Много было сказано о концепции и принципе реализации сопрограмм C++ 20. Далее используется простой пример сопрограммы C++ 20, чтобы показать, как работают сопрограммы.


картина

Простой пример сопрограммы C++20


Этот пример очень прост: отправьте сопрограмму в поток через co_await и напечатайте идентификатор потока.

#include <coroutine>
#include <iostream>
#include <thread>

namespace Coroutine {
  struct task {
    struct promise_type {
      promise_type() {
        std::cout << "1.create promie object\n";
      }
      task get_return_object() {
        std::cout << "2.create coroutine return object, and the coroutine is created now\n";
        return {std::coroutine_handle<task::promise_type>::from_promise(*this)};
      }
      std::suspend_never initial_suspend() {
        std::cout << "3.do you want to susupend the current coroutine?\n";
        std::cout << "4.don't suspend because return std::suspend_never, so continue to execute coroutine body\n";
        return {};
      }
      std::suspend_never final_suspend() noexcept {
        std::cout << "13.coroutine body finished, do you want to susupend the current coroutine?\n";
        std::cout << "14.don't suspend because return std::suspend_never, and the continue will be automatically destroyed, bye\n";
        return {};
      }
      void return_void() {
        std::cout << "12.coroutine don't return value, so return_void is called\n";
      }
      void unhandled_exception() {}
    };

    std::coroutine_handle<task::promise_type> handle_;
  };

  struct awaiter {
    bool await_ready() {
      std::cout << "6.do you want to suspend current coroutine?\n";
      std::cout << "7.yes, suspend becase awaiter.await_ready() return false\n";
      return false;
    }
    void await_suspend(
      std::coroutine_handle<task::promise_type> handle)
 
{
      std::cout << "8.execute awaiter.await_suspend()\n";
      std::thread([handle]() mutable { handle(); }).detach();
      std::cout << "9.a new thread lauched, and will return back to caller\n";
    }
    void await_resume() {}
  };

  task test() {
    std::cout << "5.begin to execute coroutine body, the thread id=" << std::this_thread::get_id() << "\n";//#1
    co_await awaiter{};
    std::cout << "11.coroutine resumed, continue execcute coroutine body now, the thread id=" << std::this_thread::get_id() << "\n";//#3
  }
}// namespace Coroutine

int main() {
  Coroutine::test();
  std::cout << "10.come back to caller becuase of co_await awaiter\n";
  std::this_thread::sleep_for(std::chrono::seconds(1));

  return 0;
}

Тестовый вывод:

1.create promie object
2.create coroutine return object, and the coroutine is created now
3.do you want to susupend the current coroutine?
4.don't suspend because return std::suspend_never, so continue to execute coroutine body
5.begin to execute coroutine body, the thread id=0x10e1c1dc0
6.do you want to suspend current coroutine?
7.yes, suspend becase awaiter.await_ready() return false
8.execute awaiter.await_suspend()
9.new thread lauched, and will return back to caller
10.come back to caller becuase of co_await awaiter
11.coroutine resumed, continue execcute coroutine body now, the thread id=0x700001dc7000
12.coroutine don't return value, so return_void is called
13.coroutine body finished, do you want to susupend the current coroutine?
14.don'
suspend because return std::suspend_never, and the continue will be automatically destroyed, bye

Из этого вывода вы можете ясно увидеть, как создается сопрограмма, co_await ожидает окончания потока, возвращаемое значение сопрограммы после завершения потока и весь процесс уничтожения сопрограммы.

Создание сопрограммы

1, 2 и 3 в выводе показывают процесс создания сопрограммы, сначала создайте промис, а затем верните задачу через promise.get_return_object(), после чего сопрограмма будет создана.

Поведение после создания сопрограммы

После создания сопрограммы должна ли функция сопрограммы выполняться немедленно? Или сначала повесить трубку? Это поведение определяется promise.initial_suspend(), так как он возвращает ожидание std::suspend_never, он не приостанавливает сопрограмму, поэтому функция сопрограммы выполняется немедленно.

co_await ожидающий

执行协程到函数的 co_await awaiter 时,是否需要等待某个任务?返回 false 表明希望等待,于是接着进入到 awaiter.wait_suspend(),并挂起协程,在 await_suspend 中创建了一个线程去执行任务(注意协程具柄传入到线程中了,以便后面在线程中恢复协程),之后就返回到 caller了,caller 这时候可以不用阻塞等待线程结束,可以做其它事情。注意:这里的 awaiter 同时也是一个 awaitable,因为它支持 co_await。

更多时候我们在线程完成之后才去恢复协程,这样可以告诉挂起等待任务完成的协程:任务已经完成了,现在可以恢复了,协程恢复后拿到任务的结果继续执行。

协程恢复

当线程开始运行的时候恢复挂起的协程,这时候代码执行会回到协程函数继续执行,这就是最终的目标:在一个新线程中去执行协程函数的打印语句。

协程销毁

awaiter.final_suspend 决定是否要自动销毁协程,返回 std::suspend_never 就自动销毁协程,否则需要用户手动去销毁。

协程的“魔法”

再回过头来看协程函数:

task test() {
    std::cout << std::this_thread::get_id() << "\n";
    co_await awaiter{};
    std::cout << std::this_thread::get_id() << "\n";
}
输出结果显示 co_await 上面和下面的线程是不同的,以 co_await 为分界线,co_await 之上的代码在一个线程中执行,co_await 之下的代码在另外一个线程中执行,一个协程函数跨了两个线程,这就是协程的“魔法”。本质是因为在另外一个线程中恢复了协程,恢复后代码的执行就在另外一个线程中了。

另外,这里没有展示如何等待一个协程完成,简单的使用了线程休眠来实现等待的,如果要实现等待协程结束的逻辑,代码还会增加一倍。

相信你通过这个简单的例子对 C++20 协程的运行机制有了更深入的理解,同时也会感叹,协程的使用真的只适合库作者,普通的开发者想用 C++20 协程还是挺难的,这时就需要协程库了,协程库可以大幅降低使用协程的难度。


картина

为什么需要一个协程库


通过前面的介绍可以看到,C++20 协程还是比较复杂的,它的概念多、细节多,又是编译器生成的模板框架,又是一些可定制点,需要了解如何和编译器生成的模板框架协作,这些对于普通的使用者来说光理解就比较吃力,更逞论灵活运用了。

这时也可以理解为什么当初 Google 吐槽这样的协程提案难于理解、过于灵活了,然而它的确可以让我们仅需要通过定制化一些特定方法就可以随心所欲的控制协程,还是很灵活的。

总之,这就是 C++20 协程,它目前只适合给库作者使用,因为它只提供了一些底层的协程原语和一些协程暂停和恢复的机制,普通用户如果希望使用协程只能依赖协程库,由协程库来屏蔽这些底层细节,提供简单易用的 API。因此,我们迫切需要一个基于 C++20 协程封装好的简单易用的协程库。

正是在这种背景下,C++20 协程库 async_simple(https://github.com/alibaba/async_simple)就应运而生了!

阿里巴巴开发的 C++20 协程库,目前广泛应用于图计算引擎、时序数据库、搜索引擎等在线系统。连续两年经历天猫双十一磨砺,承担了亿级别流量洪峰,具备非常强劲的性能和可靠的稳定性。

async_simple 现在已经在 GitHub 上开源,有了它你在也不用为 C++20 协程的复杂而苦恼了,正如它的名字一样,让异步变得简单。

接下来我们将介绍如何使用 async_simple 来简化异步编程。


картина

async_simple 让协程变得简单


async_simple 提供了丰富的协程组件和简单易用的 API,主要有:

  1. Lazy:lazy 求值的无栈协程

  2. Executor:协程执行器

  3. 批量操作协程的 API:collectAll 和 collectAny

  4. uthread:有栈协程

关于 async_simple 的更多介绍和示例,可以看 GitHub(https://github.com/alibaba/async_simple/tree/main/docs/docs.cn)上的文档。

有了这些常用的丰富的协程组件,我们写异步程序就变得很简单了,通过之前打印线程 id 例子来展示如何使用 async_simple 来实现它,也可以对比下用协程库的话,代码会简单多少。

#include "async_simple/coro/Lazy.h"
#include "async_simple/executors/SimpleExecutor.h"

Lazy<void> PrintThreadId(){
    std::cout<<"thread id="<<std::this_thread::get_id()<<"\n";
    co_return;
}

Lazy<void> TestPrintThreadId(async_simple::executors::SimpleExecutor &executor){
    std::cout<<"thread id="<<std::this_thread::get_id()<<"\n";
    PrintThreadId().via(&executor).detach();
    co_return;
}

int main() {
    async_simple::executors::SimpleExecutor executor(/*thread_num=*/1);
    async_simple::coro::syncAwait(TestPrintThreadId(executor));
    return 0;
}

借助 async_simple 可以轻松地把协程调度到 executor 线程中执行,整个代码变得非常清爽,简单易懂,代码量相比之前少得多,用户也不用去关心 C++20 协程的诸多细节了。

借助 async_simple 这个协程库,可以轻松的让 C++20 协程这只“王谢堂前燕,飞入寻常百姓家”!

async_simple 提供了很多 example,比如使用 async_simple 开发 http client、http server、smtp client 等示例,更多 Demo 可以看 async_simple 的 demo example(https://github.com/alibaba/async_simple/blob/main/demo_example)。


картина

性能


使用 async_simple 中的 Lazy 与 folly 中的 Task 以及 cppcoro 中的 task 进行比较,对无栈协程的创建速度与切换速度进行性能测试。需要说明的是,这只是一个高度裁剪的测试用于简单展示 async_simple,并不做任何性能比较的目的。而且 Folly::Task 有着更多的功能,例如 Folly::Task 在切换时会在 AsyncStack 记录上下文以增强程序的 Debug 便利性。

测试硬件

CPU: Intel® Xeon® Platinum 8163 CPU @ 2.50GHz

测试结果

单位: 纳秒,数值越低越好。

картина

картина

测试结果表明 async_simple 的性能还是比较出色的,未来还会持续去优化改进。


картина

总结


C++20 协程像一台精巧的“机器”,虽然复杂,但非常灵活,允许我们去定制化它的一些“零件”,通过这些定制化的“零件”我们可以随心所欲的控制这台“机器”,让它帮我们实现任何想法。

正是这种复杂性和灵活性让 C++20 协程的使用变得困难,幸运的是我们可以使用工业级的成熟易用的协程库 async_simple 来简化协程的使用,让异步变得简单!

参考资料:

  • https://github.com/alibaba/async_simple

  • https://timsong-cpp.github.io/cppwp/n4868/

  • https://blog.panicsoftware.com/coroutines-introduction/

  • https://lewissbaker.github.io/

  • https://juejin.cn/post/6844903715099377672

  • https://wiki.tum.de/download/attachments/93291100/Kolb%20report%20-%20Coroutines%20in%20C%2B%2B20.pdf

作者:祁宇,Modern C++ 开源社区 purecpp.org 创始人,《深入应用 C++11》作者
许传奇,阿里巴巴开发工程师, LLVM Committer, C++ 标准委员会成员
韩垚,阿里巴巴工程师,目前从事搜索推荐引擎开发

картина

END

新程序员001-004》全面上市,对话世界级大师,报道中国IT行业创新创造


картина

— 推荐阅读 —
☞Лей Цзюнь подал в отставку с поста директора Xiaomi Youpin и по-прежнему владеет 70% акций; Маск решил не вступать в правление Twitter; обнародован исходный код ядра OnePlus 10 Pro | Geek Headlines
☞ Обновление уязвимости? Хакеры могут развернуть вредоносное ПО Mirai, используя уязвимость Spring Framework
☞ "В отместку за увольнение из компании я изменил все комментарии кода проекта!"

—Нажмите здесь ↓↓↓ Не забывайте обращать внимание на звезды~  

«Поделиться», «Нравится» и «Смотреть» одним щелчком мыши

Достичь 100 миллионов техников

картина