Здравствуйте, LaptevVV, Вы писали:
LVV>А в каких задачах корутины вот прям супер — супер? LVV>Чего раньше приходилось делать муторно и долго ?
Если посмотреть старые примеры boost::asio, то без поллитры в них не разберёшся. Обратный вызов на обратном вызове сидит и обратным вызовом погоняет. Если посмотреть код с использованием корутин, то он линейный, простой для понимания и легко поддерживаемый. Хотя насчёт последнего можно поспорить
// C с классамиstruct fn1_t {
int st; const char* value; int ¶m;
int loop() {
LOOP_BEGIN(st)
value="Idle"; LOOP_POINT
value="Started"; LOOP_POINT
value="Processing"; LOOP_POINT
while (param < 10) {
value="Waiting"; LOOP_POINT
}
value="Stopped";
LOOP_END
}
...
И что грандиозные отличия?
_>>Вариант без макросов: https://coliru.stacked-crooked.com/a/4c858ba261a0862d R>Ну и? Тебе самому-то нравится то, что ты написал?
А почему мне это должно нравиться? Это должно быть просто, работать и легко объяснимо окружащим (,tp extnf htkbubjpys[ afyfnbrjd)
Здравствуйте, kov_serg, Вы писали:
_>И что грандиозные отличия?
Мне надоело повторять по сто раз, что я признаю сравнение только полных текстов программ. Ибо для сравнения важны как реализация, так и использование. А ты продолжаешь заниматься ковырянием изюма.
И да, по моим представлениям отличия грандиозные. В одном случае понятный линейный код, в другом — обфусцированная шифровка. Макросы и goto — как вишенка на торте.
Ты думаешь, что если ты налепишь в одной строке объявления переменных, условные операторы, циклы и пр. твой код станет от этого проще? Так напиши вообще всю программу в одну строку без пробелов — сразу у всех выиграешь.
_>А почему мне это должно нравиться? Это должно быть просто, работать и легко объяснимо окружащим (,tp extnf htkbubjpys[ afyfnbrjd)
Ну, приятного аппетита. Что тут ещё сказать.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Мне надоело повторять по сто раз, что я признаю сравнение только полных текстов программ. Ибо для сравнения важны как реализация, так и использование. А ты продолжаешь заниматься ковырянием изюма.
Вы за деревьями не видите леса. Я говорю что концептуально это тоже самое но значительно проще. И доступно еще с 80-х годов.
Но это требует соблюдения некоторых ограничений и требует дисциплины.
R>И да, по моим представлениям отличия грандиозные. В одном случае понятный линейный код, в другом — обфусцированная шифровка. Макросы и goto — как вишенка на торте.
Мдя. Вам шашечи или ехать. Результирующий код ничуть не хуже чем в C++20 и даже лучше. Нет динамической аллокации, состояние явное и может быть сохранено. Количество сущностей на порядок меньше.
R>Ты думаешь, что если ты налепишь в одной строке объявления переменных, условные операторы, циклы и пр. твой код станет от этого проще? Так напиши вообще всю программу в одну строку без пробелов — сразу у всех выиграешь.
На вкус и цвет фломастеры разные. Я объединяю одну логическую операцию на строку и мненя не смущает что она может состоять из нескольких простых операций. Не нравится есть автоформатер.
R>Ну, приятного аппетита. Что тут ещё сказать.
Художника может обидеть каждый. Не каждый может убежать.
Здравствуйте, landerhigh, Вы писали:
_>>Вариант без макросов: https://coliru.stacked-crooked.com/a/4c858ba261a0862d L>Ой (дальше роскомнадзор)
Да у меня чтобы попась на godbolt надо обходить блокировки, причем чем дальше тем всё более изощренными способами.
_>>Что именно вы хотите сравнивать?
Вы же тоже использовали C++23, а не C++20 так что там тоже есть изюм для сравнения причем не мало. И если его не видно это не значит что его не надо объяснять другим. Более того в виду сложности концепции понимание её у разных людей может отличаться, что приводит к прикольным спецэффектам.
Я просто предлагаю простую декомпозицию. Асинхронность отдельно, корутины отдельно. Всё явно, без "магии". Легко объяснить, мало сущностей. Реализация доступна без сторонних библиотек на любом компиляторе C или C++. Вообще на любом. То что это может не нравиться эстетам, мы переживём.
S>До тех пор пока в отладке не придется разбираться во что превращается каждый co_await, co_return и co_yield.
Ну, это же С++, легкость отладки для слабоков.
S>Из того, что вы описали про "преимущества" stackless-короутин складывается устойчивое ощущение, что все тоже самое было бы еще гораздо проще и удобнее со stackfull-короутинами.
Я писал про корутины вообще. Я соглашусь, что stackfull-короутины удобнее и красивее, и лично я предпочел бы именно их видеть в стандарте.
Но stackfull-короутины можно относительно красиво реализовать без добавления поддержки в языке и вообще есть довольно много вполне годных реализаций в том числе и в Бусте. То есть по идее одно другому не мешает.
S>Где был бы линейный код без мусорных co_, аналогичный тому, чтобы написали бы "в лоб" на голых нитях, но без оверхэда этих самых голых нитей.
Ну, код все-таки не совсем был бы аналогичным, вместо всех этих co_ все равно нужно вызывать yield() везде, где хочешь отдать управление. Отладка кооперативной многозадачности тоже ооочень веселый и увлекательный процесс, как бы она ни была реализована, хотя в любом случае проще чем отлаживать multi-threading.
Здравствуйте, ksandro, Вы писали:
K>Ну, код все-таки не совсем был бы аналогичным, вместо всех этих co_ все равно нужно вызывать yield() везде, где хочешь отдать управление.
А зачем?
Прелесть stackfull-короутин в том, что ты пишешь обычный код (как при классическом многопоточном программировании).
А моменты переключения делаются автоматически под капотом функций, которые могут заблокировать выполнение (например, read/write/accept/listen и т.п.).
Здравствуйте, ksandro, Вы писали:
K>Ну, код все-таки не совсем был бы аналогичным, вместо всех этих co_ все равно нужно вызывать yield() везде, где хочешь отдать управление. Отладка кооперативной многозадачности тоже ооочень веселый и увлекательный процесс, как бы она ни была реализована, хотя в любом случае проще чем отлаживать multi-threading.
Господа, а что вы вкладываете в понятие "отладка (кооперативной многозадачности|многопоточности)" и почему это такая большая проблема? Для друга интересуюсь.
Здравствуйте, so5team, Вы писали:
S>Прелесть stackfull-короутин в том, что ты пишешь обычный код (как при классическом многопоточном программировании). S>А моменты переключения делаются автоматически под капотом функций, которые могут заблокировать выполнение (например, read/write/accept/listen и т.п.).
Которые и должны вызвать этот yield().
Да, весь код по стеку будет выглядеть как самый обычный многопоточный блокирующий код. И ему совершенно не обязательно знать, что он выполняется в контексте корутины. Что есть плюс.
А вот то, что они таки стековые, могут оказаться не просто минусом, а дилбрейкером.
_>И что грандиозные отличия?
Самое грандиозное отличие, что это first-class штука, поддерживаемая языком. А это значит, что можно это дело легко рефакторить, например, вынести какие-то части в переиспользуемые функции, навигация по коду, отладчик, етс. Или интегрировать с другими фичами языка, например, классы/шаблоны/тд. Хотел бы я взглянуть на то как ты подружишь свои макросы с шаблонами. Хотя, наверное нет, не хотел бы, глаза жалко. Понятно, что из буханки можно сделать троллейбус... но зачем?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, so5team, Вы писали:
L>>А вот то, что они таки стековые, могут оказаться не просто минусом, а дилбрейкером. S>Когда нет выбора, тогда и нет предмета разговора.
Вот, похоже, именно поэтому в стандарт попали stackless корутины.
Здравствуйте, ·, Вы писали:
_>>И что грандиозные отличия? ·>Самое грандиозное отличие, что это first-class штука, поддерживаемая языком. А это значит, что можно это дело легко рефакторить, например, вынести какие-то части в переиспользуемые функции, навигация по коду, отладчик, етс. Или интегрировать с другими фичами языка, например, классы/шаблоны/тд. Хотел бы я взглянуть на то как ты подружишь свои макросы с шаблонами. Хотя, наверное нет, не хотел бы, глаза жалко. Понятно, что из буханки можно сделать троллейбус... но зачем?
За тем постепенно исполняемые функции это тривиальный концепт. А корутины в C++20 это знатный айсберг.
Вот first-class штука для FSM на корутинах. Вот где изюма можно насушить
#include <cassert>
#include <coroutine>
#include <unordered_map>
#include <vector>
#include"gtest/gtest.h"namespace {
template <typename T> struct generator {
struct promise_type {
T current_value;
using coro_handle = std::coroutine_handle<promise_type>;
auto get_return_object() { return coro_handle::from_promise(*this); }
auto initial_suspend() { return std::suspend_always(); }
auto final_suspend() noexcept { return std::suspend_always(); }
void return_void() {}
void unhandled_exception() { std::terminate(); }
auto yield_value(T value) {
current_value = value;
return std::suspend_always{};
}
};
using coro_handle = std::coroutine_handle<promise_type>;
bool move_next() {
return handle_ ? (handle_.resume(), !handle_.done()) : false;
}
T current_value() const { return handle_.promise().current_value; }
generator(coro_handle h) : handle_(h) {}
generator(generator const &) = delete;
generator(generator &&rhs) : handle_(rhs.handle_) { rhs.handle_ = nullptr; }
~generator() {
if (handle_)
handle_.destroy();
}
private:
coro_handle handle_;
};
struct suspend_tunable {
bool tune_;
suspend_tunable(bool tune = true) : tune_(tune) {}
bool await_ready() const noexcept { return tune_; }
void await_suspend(std::coroutine_handle<>) const noexcept {}
void await_resume() const noexcept {}
};
struct resumable_cancelable {
struct promise_type {
bool is_cancelled = false;
using coro_handle = std::coroutine_handle<promise_type>;
auto get_return_object() { return coro_handle::from_promise(*this); }
auto initial_suspend() { return std::suspend_always(); }
auto final_suspend() noexcept { return std::suspend_always(); }
void return_void() {}
void unhandled_exception() { std::terminate(); }
auto await_transform(std::suspend_always) {
if (is_cancelled)
return suspend_tunable{true};
return suspend_tunable{false};
}
};
using coro_handle = std::coroutine_handle<promise_type>;
resumable_cancelable(coro_handle handle) : handle_(handle) { assert(handle); }
resumable_cancelable(resumable_cancelable &) = delete;
resumable_cancelable(resumable_cancelable &&rhs) : handle_(rhs.handle_) {
rhs.handle_ = nullptr;
}
void cancel() {
if (handle_.done())
return;
handle_.promise().is_cancelled = true;
handle_.resume();
}
bool resume() {
if (!handle_.done())
handle_.resume();
return !handle_.done();
}
~resumable_cancelable() {
if (handle_)
handle_.destroy();
}
private:
coro_handle handle_;
};
struct resumable_noinc {
struct promise_type {
using coro_handle = std::coroutine_handle<promise_type>;
auto get_return_object() { return coro_handle::from_promise(*this); }
auto initial_suspend() { return std::suspend_always(); }
auto final_suspend() noexcept { return std::suspend_always(); }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
using coro_handle = std::coroutine_handle<promise_type>;
resumable_noinc(coro_handle handle) : handle_(handle) { assert(handle); }
resumable_noinc(resumable_noinc &) = delete;
resumable_noinc(resumable_noinc &&rhs) : handle_(rhs.handle_) {
rhs.handle_ = nullptr;
}
bool resume() {
if (!handle_.done())
handle_.resume();
return !handle_.done();
}
~resumable_noinc() {
if (handle_)
handle_.destroy();
}
coro_handle handle() {
coro_handle h = handle_;
handle_ = nullptr;
return h;
}
private:
coro_handle handle_;
};
using coro_t = std::coroutine_handle<>;
template <typename State, typename Sym> class state_machine {
State current_;
std::unordered_map<State, coro_t> states_;
generator<Sym> gen_;
public:
state_machine(generator<Sym> &&g) : gen_{std::move(g)} {}
void run(State initial) {
current_ = initial;
states_[initial].resume();
}
template <typename F> void add_state(State x, F stf) {
states_[x] = stf(*this).handle();
}
coro_t operator[](State s) { return states_[s]; }
State current() const { return current_; }
template <typename F> auto get_awaiter(F transition);
Sym genval() const { return gen_.current_value(); }
void gennext() { gen_.move_next(); }
};
template <typename F, typename Sym, typename SM> struct stm_awaiter : public F {
SM &stm_;
stm_awaiter(F f, SM &stm) : F{f}, stm_{stm} {}
bool await_ready() const noexcept { return false; }
coro_t await_suspend(coro_t) noexcept {
stm_.gennext();
auto sym = stm_.genval();
auto newstate = F::operator()(sym);
return stm_[newstate];
}
bool await_resume() noexcept { return (stm_.genval() == Sym::Term); }
};
template <typename State, typename Sym>
template <typename F>
auto state_machine<State, Sym>::get_awaiter(F transition) {
return stm_awaiter<F, Sym, decltype(*this)>(transition, *this);
}
}
namespace {
enum class State : char { A, B };
enum class Sym : char { A, B, Term };
using stm_t = state_machine<State, Sym>;
generator<Sym> input_seq(std::string s) {
for (char c : s) {
switch (std::tolower(c)) {
case'a':
co_yield Sym::A;
break;
case'b':
co_yield Sym::B;
break;
default:
co_yield Sym::Term;
break;
}
}
for (;;)
co_yield Sym::Term;
}
resumable_noinc StateA(stm_t &stm) {
auto transition = [](auto sym) {
if (sym == Sym::B)
return State::B;
return State::A;
};
for (;;) {
std::cout << "State A" << std::endl;
bool finish = co_await stm.get_awaiter(transition);
if (finish)
break;
}
}
resumable_noinc StateB(stm_t &stm) {
auto transition = [](auto sym) {
if (sym == Sym::A)
return State::A;
return State::B;
};
for (;;) {
std::cout << "State B" << std::endl;
bool finish = co_await stm.get_awaiter(transition);
if (finish)
break;
}
}
}
TEST(coroutines, fsm) {
auto gen = input_seq("aaabbaba");
stm_t stm{std::move(gen)};
stm.add_state(State::A, StateA);
stm.add_state(State::B, StateB);
stm.run(State::A);
auto Cur = stm.current();
EXPECT_EQ(Cur, State::A);
}
// crutch for clang on godbolt#if defined(NOGTESTMAIN)
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#endif
Здравствуйте, landerhigh, Вы писали:
L>>>А вот то, что они таки стековые, могут оказаться не просто минусом, а дилбрейкером. S>>Когда нет выбора, тогда и нет предмета разговора.
L>Вот, похоже, именно поэтому в стандарт попали stackless корутины.
ЕМНИП, стековые не стали добавлять потому что их можно реализовать и без внесения изменений в язык, что к тому времени уже неоднократно проделывалось в разных библиотеках. Тогда как для безстековых нужны изменения в языке и поддержка со стороны компилятора. При этом, ЕМНИП в очередной раз, выбранный подход многократно критиковался как во время работы над C++20, так и после его принятия.
Здравствуйте, landerhigh, Вы писали:
L>Господа, а что вы вкладываете в понятие "отладка (кооперативной многозадачности|многопоточности)" и почему это такая большая проблема? Для друга интересуюсь.
Очень просто потому как многозадачность в c++ не структурированая. Поэтому и отладка превращается в: поймай угря в ведре с угрями.