Как стать автором
Обновить
25
0
Александр @Doktor3lo

Chief of R&D

Отправить сообщение

Выглядит так, как будто вы изобрели собственный docusaurus. Смотрели ли вы в его сторону, можете написать чем ваше решение лучше? Ну кроме того, что оно ваше :)

Хм, как же её тогда терминейтить?

Никак. Но я ведь не утверждаю, что надо мочь прерывать абсолютно все. Как раз на моей практике это не так. Чаще приходится прерывать "бесконечные" корутины или же awaitable объекты, которые реализованы через корутину, как часть тех же "бесконечных" корутин. А большинства проще и правильнее просто дождаться.

Данная реализация, так сказать, универсальная. Но я согласен, что эффективнее с точки зрения ресурсов и производительности использовать специфичные реализации под разные задачи.

В целом вы правы, но пример я уже не буду исправлять. Иначе наша переписка потеряет смысл :)

"Овнить" корутину в task - это вариант. Для моего примера он пожалуй даже лучше - проще. Но я использую shared_ptr, чтобы иметь возможность просто запустить корутину как обычную функцию и забыть про нее. Не скажу, что это идеальное решение, так как по сути происходит неявный запуск потока, и я подумываю от такого подхода отказаться в пользу spawn как в boost. Но пока я им пользуюсь.

Второй предложенный вами вариант - откровенно сложнее. И что он дает? Экономит один malloc (и то не всегда, где-то же вы список корутин будете хранить)

Можно применив еще некоторое количество шаблонной магии добиться такого:

task coroutine(channel<int>::out out1, channel<int>::out out2, channel<std::string>::out out3) {
  auto result = co_await select(out1, out2, out3);
  if (auto *value = result.get_if(out1); value != nullptr) {
    std::cout << "got int    " << *value << std::endl;
  } else if (auto *value = result.get_if(out2); value != nullptr) {
    std::cout << "got int2 " << *value << std::endl;
  } else if (auto *value = result.get_if(out3); value != nullptr) {
    std::cout << "got string " << *value << std::endl;
  }
}

У меня получилась следующая реализация.

using channel_id = void *;

template <typename Type>
class channel {
  // ...
  class out {
    // ...
    channel_id id() const noexcept { return reinterpret_cast<channel_id>(m_self); }
  };
};

// unique_variant_t я просто нагуглил, но вроде работает
template <typename T, typename... Ts>
struct filter_duplicates { using type = T; };

template <template <typename...> class C, typename... Ts, typename U, typename... Us>
struct filter_duplicates<C<Ts...>, U, Us...>
        : std::conditional_t<(std::is_same_v<U, Ts> || ...)
          , filter_duplicates<C<Ts...>, Us...>
                , filter_duplicates<C<Ts..., U>, Us...>> {};

template <typename T>
struct unique_variant;

template <typename... Ts>
struct unique_variant<std::variant<Ts...>> : filter_duplicates<std::variant<>, Ts...> {};

template <typename T>
using unique_variant_t = typename unique_variant<T>::type;

// получить значение из результата по ссылке на канал
// замена std::get_if
template <typename ...Channels>
struct select_result {
  unique_variant_t<std::variant<typename Channels::value_type ...>> value;
  channel_id channel;
  template <channel_out Channel>
  auto get_if(const Channel &c) -> typename Channel::value_type * {
    if (c.id() == channel) {
      return &std::get<typename Channel::value_type>(value);
    }
    return nullptr;
  }
};

template <channel_out ...Channels>
auto select(Channels ...channels) {
  struct [[nodiscard]] awaitable {
    using handle = std::experimental::coroutine_handle<>;
    std::tuple<Channels...> channels;
    std::shared_ptr<handle> coro;

    bool await_ready() const {
      return std::apply([](auto &...values) {
        return (values.await_ready() || ...);
      }, channels);
    }

    void await_suspend(handle c) {
      coro = std::make_shared<handle>(c);
      std::apply([this](auto &...values) {
        (values._suspend(coro), ...);
      }, channels);
    }

    auto await_resume() {
      select_result<Channels...> result;
      std::apply([&result](auto & ...values) {
        return ([&result] <typename Type> (Type &value) {
          if (value.await_ready()) {
            result.value.template emplace<typename Type::value_type>(value.await_resume());
            result.channel = value.id();
            return true;
          }
          return false;
        }(values) || ...);
      }, channels);
      return std::move(result);
    }
  };
  return awaitable{std::make_tuple(channels...)};
}

Видел, знаю, практикую, но если вы про реализацию через std::variant

task coroutine(channel<int>::out out1, channel<std::string>::out out2) {
  auto result = co_await select(out1, out2);
  if (auto *v = std::get_if<int>(&result); v != nullptr) {
    std::cout << "got int " << *v << std::endl;
  } else if (auto *v = std::get_if<std::string>(&result); v != nullptr) {
    std::cout << "got string " << *v << std::endl;
  }
}

Как вы обработаете однотипные каналы? std::variant позволяет хранить один и тот же тип в нескольких разных позициях одновременно, но вот сделать для них различную обработку вашим способом уже не получится. Кроме того, в вашем примере куча лямбд - читайте мои сноски, там описано чем это вам грозит. Мой же вариант с лямбдами будет интереснее, потому что вам не надо отдельно перечислять прослушиваемые каналы, а отдельно их обрабатывать (один источник истины).

Ваш пример выглядит красиво, но в данном случае собирает в себе худшие свойства обоих приведенных мной.

Написал длинный ответ и понял, что пишу не про то :) Вопрос отличный!

Если кратко, в вашем примере я проблем не вижу. Всё должно корректно отработать. Могу предположить, что в вашем примере после завершения цикла программа зависнет на полсекунды в деструкторе future, который будет вызван методом destroy() из деструктора generator.

Все локальные переменные корутины хранятся не в стеке, а в куче. Они будут освобождены вызовом m_coro.destroy() в порядке, обратном порядку создания. Тут действительно возможны подводные камни. Например, если вы используете конструкцию try { ... } catch для освобождения каких-то ресурсов. Если у вас везде RAII - проблем не будет.

Чтобы проверить самого себя, можно задать себе вопрос: что будет, если в момент одной из итераций co_yield превратится в return (для корутины co_return)? Если это не приведет к катастрофе, то и корутина нормально отработает.

То что мне показалось интересным и касалось непосредственно корутин - я описал в комментариях. Решение описанных проблем - это скорее умение работать с std::exception_ptr и std::variant (ну или более традиционно - через указатели). Если погружаться во все детали - многовато получится :)

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

Я это понимаю. Строго говоря и лямбды тоже сахар. Я без них больше 10 лет жил (писал на c++98), использовал классы и не обламывался.

Забавны два момента:
1. как всевозможные языки, изначально типизации не имевшие, дрейфуют в сторону классической статической типизации
2. и то, с каким упорством авторы различных языков придумывают новые названия для существующих паттернов. Как будто слова концепт, интерфейс и тому подобные защищены авторским правом :)
Не очевидный для вас пример привел. Погуглите спортивные ролики — они так то, по частям продаются, как компы (память там, проц, винт). Только вы привыкли к ним как к готовому решению, про альтернативу и не слышали (оно вам и не надо было). А вот с сайтами, вы привыкли к сложному пути, вы его понимаете, но это не значит, что он прост и понятен всем.
Сервер для чего? В чем принципиальное отличие «настройки сервера» в ISPManager и в VESP? В том, что в первом можно что-то поправить, а второй как есть?

А вот таки ДА! Зачем предоставлять человеку выбор суть которого он не понимает?

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

Пример той же настройки PHP — выберет он PHP как модуль Apache и привет, ни плагин из панели wordpress поставить ни обновиться (можно конечно, но «из коробки» уже не заработает). А еще могут быть нюансы с версией PHP, требуемой для того же WP.
Допустим…
А кому принадлежит домен, зарегистрированный в 2 клика? За 2 клика особо данных не забьешь)
А где находятся NS сервера 2х кликового домена?
Ну и главный вопрос. А если мне нужен не Wordpress?

Домен будет принадлежать вам. Если вы его будете покупать — там форма есть, чтобы стрясти с вас необходимую информацию (вы правы — не два клика). NS настраиваются в самом VEPP (настраиваются провайдером заранее — вам об этом думать не надо. В качестве NS сервера может использоваться наш DNSmanager 5)
Если у вас CentOS 6 или FreeBSD — вам вообще панель не нужна. Вы, должно быть, весьма старый и опытный боец и сами всё умеете :)
А Ubuntu будет! Там вся настройка через ansible идет. Cookbook адаптируем под эти ОС и будет работать.
Про русский язык — согласен, остальное — весьма спорно
У нас в проекте одиннадцатый стандарт буквально недавно только разрешили использовать. :)

И вообще, завидуйте молча ;)
Нет. И, они работают только с нашими продуктами написанными на core. Вообще, они входят в состав COREmanager. Но, можем подумать и об opensource ;)
В оригинальном документе было, но в итоге осталось за рамками… :)
Пробовали, в том числе и сами студенты по собственной инициативе. Так то, проблем с рабочими местами у нас нет. Но качественный код — пока не видел.
Прикольно, не знал. Можно считать не зря потратил время, пока писал!
Я и не спорю. Для этого используется PAX header. По сути, для таких файлов, он внутри архива создает еще один служебный файл, куда и записывается вся информация, которая в стандартный заголовок tar не вошла. А в стандартном заголовке, ограничения те, которые я написал.
В целом, согласен. Но, это когда резервное копирование делается «с любовью» и под себя. Для массовой услуги такое будет проблематично.
Опять же, не всякую базу можно сдампить за конечное время. У нас база биллинга пару сотен гигабайт занимает. На её дамп невесть сколько времени уйдет. Про восстановление я вообще молчу.
В общем, тут много но. В этом направлении написана вторая часть данного рассказа…
1

Информация

В рейтинге
Не участвует
Откуда
Иркутск, Иркутская обл., Россия
Работает в
Дата рождения
Зарегистрирован
Активность