Войти
ПрограммированиеФорумОбщее

С++ корутины, начальный уровень (комментарии)

Страницы: 1 2 3 4 Следующая »
#0
13:49, 7 ноя 2022

С++ корутины, начальный уровень (комментарии)

Это сообщение сгенерировано автоматически.

#1
(Правка: 13:49) 13:49, 7 ноя 2022

Во, теперь гораздо лучше. Опубликовал.

#2
5:19, 8 ноя 2022

На первой странице опечатка "обычно защищен мютектом"

#3
(Правка: 8:38) 8:23, 8 ноя 2022

/A\
> ажется я слышал анекдот, про то, что некоторые читают только заключение)
поэтому ты решил сделать жизнь труднее всем остальным и заключение не писать?

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

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

> Как и первый код, корутина прерывается два раза - явно при вызове co_await, но остановки потоков не происходит и в промежутках выполняются другие задачи.
что значит "вызов co_await"? co_await — это ключевое слово, которое ты впервые упомянул в предыдущей строке, а при объяснении ты подразумеваешь, что читающий знает, что оно делает и вообще к чему относится. при этом смысл этого ключевого слова объясняется только на следующей странице, но даже после объяснения его функциональности мне лично не ясен синтаксис, к чему именно оно пристыковывается и как.

также не ясно, какие именно задачи будут выполняться во время прерывания.

ты перечисляешь методы классов вроде Promise/Awater/Task, но не объясняешь ни смысл задачи, которую они решают, ни то, откуда они берутся: их предполагается писать под каждую задачу самостоятельно? брать из готовых библиотек?

#4
(Правка: 9:20) 9:18, 8 ноя 2022

Suslik
> поэтому ты решил сделать жизнь труднее всем остальным и заключение не писать?
Когда я впервые почитал по с++ корутинам мое мнение было - слишком сложно все самому писать, подожду когда все в стандарт добавят. Но такое в заключение писать не хочется)
Я думал взять примеры использования cppcoro, чтоб показать как в итоге будут/должны выглядить корутины, но это как после вывода тругольника сразу объяснять рендер сцены)
Кстати, хорошая аналогия - я тут 8 страниц объясняю как нарисовать треугольник))

> удобны в сравнении с другими языками, насколько они практичны
Это же с++, тут все неудобно и непрактично, зато максимально гибко. В других языках я не использовал корутины, поэтому мало что могу сказать.

> что значит "вызов co_await"?
Так в другом коде тоже нет реализации, объяснений тредпула, промисов и тд. Это псевдокод, чтобы показать смысл корутин.
Вот то, что дальше мало понятно - это плохо, посмотрю еще что улучшить. Но сами корутины настолько сложные, что лучше начинать с примеров. В стандарте на co_await 3 варианта написания и много-много нюансов, без примера, который можно запустить и пройтись дебагером по строчкам все очень не просто.

> также не ясно, какие именно задачи будут выполняться во время прерывания.
Какие сам запустишь. Я же не могу скопировать 8 страниц во введение, чтобы все объяснить)

> их предполагается писать под каждую задачу самостоятельно? брать из готовых библиотек?
Их придется самому писать под каждую корутину, а иногда и по несколько.
Библиотека есть только cppcoro, но и там что-то недописано, а что-то только под винду (файлы например).

#5
(Правка: 9:32) 9:29, 8 ноя 2022

/A\
вот, например, thread pool. можно прекрасно в одном предложении описать, что он делает и парой строчек кода показать, как это может реализовываться. при этом thread pool прекрасно можно написать без модификации компилятора, просто как библиотеку на существующем стандарте. мне всё ещё не понятно, какая именно часть корутин должна быть частью компилятора и почему. или речь идёт только о том, чтобы сделать работу с ними более удобной?

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

> Какие сам запустишь. Я же не могу скопировать 8 страниц во введение, чтобы все объяснить)
надо не добавлять информацию, а переформулировать, чтобы меньше вопросов возникало. например, под "другими задачами" понимаются другие объявленные тобой корутины, либо другие процессы ОС? из объяснения это не ясно.

#6
(Правка: 9:46) 9:44, 8 ноя 2022

Suslik
> чтобы меньше вопросов возникало. например, под "другими задачами" понимаются
> другие объявленные тобой корутины, либо другие процессы ОС? из объяснения это
> не ясно.
Там написано "Корутины это другой синтаксис для использования планировщика задач." Я имел ввиду, что корутины работают поверх thread pool, только вместо тасков - корутины.

В компилятор добавили сохранение состояния корутины (локальные переменные, они же без стэковые), остальное написано кодом через операторы.

#7
10:02, 8 ноя 2022

думаю, что нужно написать, что-то про thread pool, который реализуется на уровне библиотеки, в котором сами потоки управляются библиотекой, реализующей пул, а пользователь подсовывает пулу таски. в твоём примере кода с thread pool это никак не фигурирует, но в моём понимании сам пул — это объект, и новые таски создаются не просто в воздухе, а назначаются конкретному пулу. короче, я бы как-то так написал:

threadPool.AddTask(Task([]() -> Buffer
{
  return GetBuffer();
}).then([](Buffer buf) -> std::tuple<Buffer, File>
{
  return std::tie<buf, OpenFile("some file")>;
}).then([](std::tuple<Buffer, File> t)
{
  t.get<1>().Write(t.get<0>());
});

так вот в таком виде мне вообще не понятно, каким образом код с корутинами может быть эквивалентным коду на тасках.

#8
10:15, 8 ноя 2022

Suslik
> короче, я бы как-то так написал:
Вот это выглядит страшно и непонятно, я все же псевдокод пишу.

> так вот в таком виде мне вообще не понятно, каким образом код с корутинами
> может быть эквивалентным коду на тасках.
Просто поверь что это так)
Я не могу запихать все объяснение во введение.

Вариант с промисами:

auto AllocBuffer (Size size)
{
  return threadPool.Add( [size](){ return new char[size]; }, DependsOn{allocatorAsyncMutex} );
}

Вариантс корутинами:

auto operator co_await (task deps)
{
  ...
  threadPool.Add( GetCurrentTask(), DependsOn{deps} );
}

task  ReadData()
{
  auto buf = co_await AllocBuffer( 10_Mb );
  size_t readn = co_await ReadFile( "file.name", buf );
  ...
}

auto t = ReadData();
threadPool.Add( t );

Далее в примерах есть такой код, так зачем усложнять в самом начале?
Я написал статью, потому что у всех остальных это выглядит как "вот вам 100+ строк кода, сейчас расскажу как оно работает" и начинает прыгать по коду, так как выполнение идет по строчке в разных частях.

#9
10:25, 8 ноя 2022

/A\
> Вариант с промисами:
> return threadPool.Add( [size](){ return new char[size]; }, DependsOn{allocatorAsyncMutex} );
вот так понятнее, чем просто вызов глобальной функции, которая что-то там внутри у себя делает и ничего не возвращает. я бы вообще функцию выкинул и только её тело оставил.

> Далее в примерах есть такой код, так зачем усложнять в самом начале?
потому что основная сложность при написании тредпула — это осуществление синхронизации и очердёности тасков. в твоём примере псевдокода синхронизации вообще нет, тогда какой вообще смысл в promise'ах, если можно было просто два независимых таска засабмитить?

> auto operator co_await (task deps)
вот тут меня смутило больше всего, что co_await встречается перед вызовом функции. то есть по контексту не понятно, то ли его автоматически можно подсоединить к вызову любой функции, то ли что. нужно, наверное, указать, что это — оператор, который реализуется для класса тредпула(?), предоставляющего интерфейс корутин? либо для интерфейса самих тасков?

#10
10:37, 8 ноя 2022

Suslik
> в твоём примере псевдокода синхронизации вообще нет
Почему нет? .then как раз задает очередность выполнения.

> вот так понятнее, чем просто вызов глобальной функции
Могу под спойлером полную реализацию написать, для таких как ты)

> вот тут меня смутило больше всего
Так co_await посвящена половина статьи.

co_await AllocBuffer( 10_Mb );
разворачивается в:
task AllocBuffer( 10_Mb ) {...}  // корутина, промис, таск - тут без разницы
V
co_await task;
V
Awaiter operator co_await (task)
V
Awaiter::await_ready() { return task.Complete(); } // допустим вернул false
V
Awaiter::await_suspend(coroutine_handle currentTask) {
  threadPool.Add( currentTask, DependsOn{task} );
}

operator co_await можно перегрузить под любой тип данных.

#11
10:48, 8 ноя 2022

co co co программирование...

#12
11:25, 8 ноя 2022

Обновил 1 и 2 страницы

#13
(Правка: 11:44) 11:36, 8 ноя 2022

Где реально это нужно и без корутинов никак ?
Вот важный вопрос.

Лучше бы в железе процессора развивали новые фичи и подходы, чем эти с++ выкрутасы.

Ощущение что корутины для слабых процов придумали, что бы больше реализовать псевдо-потоков(срабатывающих при определенных моментах и не так часто).

И на 12 ядерном проце в 24 потока, как-то корутины не особо нужны и впечатляют.
Это мое мнение.

#14
(Правка: 11:46) 11:38, 8 ноя 2022

В JavaScript идеологически всё просто, т.к. нет явной многопоточности в языке и промисы с корутинами есть довольно изящная синтаксическая конфетка над программированием вида отложенных событий.
Традиционно ядро языка обладает способностью отложить какую то долгоиграющую задачу типа выполнения HTTP-запроса в настоящий поток который будет выполняться параллельно ядру языка и когда запрос завершиться - вызывать какую то функцию языка уже в его однопоточной среде. Т.е. у нас есть незримый пул отложенных задач которые могут в любой момент когда ядро языка стоит на паузе активироваться и выполнить какую то функция в языке как callback передав ему результат того над чем они там в фоне трудились.
В результате сперва предлагалось делать всё на callback-ах, но когда у тебя сперва HTTP-запрос ушёл в ожидание, потом вызывал callback1, потом в нём запустился парсер результата запроса в JSON, поставив в очередь callback2, далее в нём код пропарсил данные и обогащённый новыми знаниями делает новый HTTP-запрос в callback3, то код размазывается на последовательный набор функицй-коллбеков который сверху еще притаптывается и осложняется необходимостью на каждом этапе реагировать на ошибки. Особенно весело становится когда поток выполнения по разным условиям должен еще разветвляться и мешанину из то последовательно то в другом порядке выполняющихся функций-коллбеков становилось очень тяжело осознавать и контролировать.

Сперва это пытались обуздать через нанизывание лямбд вида someProcess().then().error(), но опять таки в нелинейных случаях получалось говно, а не код.

И вот тогда ввели асинхронные функции где-то типа C# подсмотрев - асинхронная функция может уснуть на слове await ожидая результата и проснуться по наступлению результата в том же месте где уснула.
Тогда вместо десятка функцкий-кэллбеков можно писать в привычном как будто-бы блокирующем, но неблокирующем стиле:

async function MakeRequest()
{
   http1 = await AsyncHTTPRequest(...);
   json1 = await ParseJSON( http1.text );
   if ( json1.someField1 )
   {
       http2 = await AsyncHTTPRequest( json1.someField2, ... );
       json2 = await ParseJSON( http2.text );
   }
}

Важно тут, что на каждом await текущая функция засыпает в ожидании выполнения откладываясь в очередь спящих функий. При этом эти функции которые можно скормить в await обязаны возвращать объект Promise. Он тут послабже опять таки нашпигован нежели в настоящей многопоточке и главная его задача сохранить не только результат, но и если в async-функции под await произошло исключение, то в Promise ложится вся нужная информация чтобы его снова активировать.
Таким образом await как ключевое слово сперва уходит в сон, а после пробуждения проверяет не содержит ли Promise который получился информации об исключении - и если да, то регенерирует это исключение в текущей функции. Иначе же возвращает результат сохранённый в Promise.
Таким образом главная задача в однопоточном JavaScript всей этой асинхронщины - выпрямить код интенсивно использующий отложенные в пул задач задачи.
В C++, конечно, при наличии реальной многопоточки вещи должны быть заметно сложнее и промисы должны иметь блокирующий метод getResult или метод проверки готов ли уже результат или нет.
Надо будет как то попытаться вникнуть.

Страницы: 1 2 3 4 Следующая »
ПрограммированиеФорумОбщее