All streams
Search
Write a publication
Pull to refresh
14
0
Косинцев Артём @Lantris

Инженер-программист

Send message

По большому счету он здесь не нужен, особенно в constexpr контексте. В целом избыточно, и уже давно игнорируется компиляторами — inlining решается оптимизатором. Если только как запасной парашют для ODR violation

Полностью согласен, покрывать и поддерживать весь sql - жизни не хватит. Статья о возможностях constexpr, на полноценный легковесный валидатор всего и вся надеяться не приходится. Сложность проверок и их количество ограничены адекватностью подхода, пока потенциальная польза перевешивает его недостатки.

Так и есть, в большей части это вопрос правильной работы в параллелизме.

Те же Senders/Receivers в C++26 тоже не больший ад, чем просто новый инструмент.

главный вопрос: все это хорошо и круто, но зачем, если для приведенных примеров можно проще и понятней?..

Инструменты нового механизма и синтаксис как-то интуитивно понятнее на самом простом.

Простыни кода и все нюансы Senders/Receivers в рамках одной статьи упихнуть точно невозможно, но от простого к сложному, шаг за шагом

Соберу обратную связь, дополню второй статьей, где будет больше реальных примеров.

Пока что идейно, но по мере дополнений будет и практическая сторона данной темы.

По случайности отправил комментарий не в эту ветку.

Как минимум, потому что у него есть еще одна полезная функция.

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

Если приходится запускать асинхронные задачи, которые требуют собственного состояния — например, буфера или соединений, но мы хотим гарантировать, что оно живёт, пока задача выполняется, то нужен let_value, ниже покажу, какие проблемы создает then:

then([] {
    auto buf = std::make_shared<std::vector<char>>(1024);
    return read_from_socket(buf); // возвращает sender<void>
}); // возвращает sender<sender<void>>

scope.spawn(task);

Невозможно передать напрямую в spawn, ожидается один уровень вложенности sender'a и главное: buf может быть уничтожен до завершения read_from_socket, т.к. then не знает, что надо продлить его жизнь.

А let_value гарантирует, что buf жив до конца всей цепочки.

Без let_valueмы имеем нестабильный lifetime данных, ручное распутывание sender<sender<U>> и во многих случаях довольно сложно написать что-то читаемое.

Кстати, scope из примера - он же async_scope - API из предложения P3149, который используется совместно с P2300

  • позволяет запускать множество асинхронных операций через .spawn(sender);

  • отслеживает их завершение;

  • предоставляет способ дождаться окончания всех операций (через .join()).

execution::async_scope scope;

scope.spawn(
  ex::let_value(ex::just(), [] {
    return ex::just(); // Любой sender
  })
);

co_await scope.join(); // Ждём завершения всех задач

Действительно, путаница вышла, я поправлю описание. Спасибо.

then действительно всегда создаёт новый sender, заворачивая в него результат вызова, но с той разницей от let_value, что последний не будет плодить sender'ы, и если возвращаемый тип уже является sender'ом - он его пробросит напрямую:

then(just(42),
    [](int x) {
        return just(x + 1); // sender<int>            
    } // then вернет sender<sender<int>>

let_value(just(42),
    [](int x) {
        return just(x + 1); // возвращает sender<int>
    } // let_value вернет sender<int>

Поэтому аккумулировать через then результат прошлых вызововthen/let_value, напрямую пробрасывая sender'ы без распаковки - чревато слоями-sender'ов.

А, вот, let_value вернет честный sender в одной плоскости -> sender<int>

Да, можно встроить несколько независимых веток обработки ошибок в один pipeline, как в вашем примере. И да, вы можете адаптировать std::expected к модели senders/receivers, но для этого потребуется свой адаптер или комбинация существующих (let_value, just_error, variant_sender).

Первый способ - преобразовать expected в sender, второй способ - танцевать с let_error. Но не факт, что получится без танцев с бубном

Эти библиотеки были и ранее, если поискать, думаю, можно много найти.

Из того, что выходит по первым кликам: Серверные примеры от NVIDIA, Библиотека Intel для микроконтроллеров, реализующая частичную поддержку P2300, + обсуждения intel о интеграции sender/receiver.

Если наткнетесь на интересные примеры - присылайте, будет, что разобрать

Да, всё именно так, лямбда создаётся в момент выполнения tag_invoke, то есть при построении sender'а ex::schedule(ps), а исполнение лямбды происходит внутри пула, как часть start(os)-операции, в примере - при вызове sync_wait(pipeline). Поэтому корректнее будет сказать, что измеряется задержка между моментом построения sender-а и моментом фактического запуска работы в пуле.

Видимо, слишком размытой получилась формулировка:

<...> выводит задержку переводa задачи в пул <...>, иначе говоря, замеряет время от вызова schedule(...) до реального начала выполнения задачи в пуле.

Речь идет о замере времени с момента описания/конструирования, до фактического старта.

Да, все именно так. Если в процессе вызова Emit(), когда мьютекс уже захвачен, один из коллбэков вызовет код, который приводит к уничтожению подписки, то возникнет дедлок

int main()
{
   Observable observable;
   // Создаем подписку, чей коллбэк вызывает отписку (симулируем удаление подписки)
   observable.Subscribe(
      [ &observable ]()
      {
         // Так как emit() уже захватил мьютекс, попытка захватить его внутри отписки приведет к дедлоку
         {
            auto tmp = observable.Subscribe( []() {} );
         }
      } );
   // Вызовем emit(), который вызовет наш коллбэк
   observable.Emit();
   // Любой код после не будет выполнен
}

Можно решить эту проблему через применение std::recursive_mutex, или копирование списка коллбэков под защитой мьютекса с последующим их вызовом вне критической секции.

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

Если вы используете этот код...

Благо, не использую его вообще нигде, разве что для примеров в статьях. Спасибо, это очень дельное замечание, о котором мне, наверное, стоило упомянуть в статье.

Information

Rating
Does not participate
Registered
Activity