Здравствуйте, artelk, Вы писали:
A>Объяснил бы мне кто-нибудь вот что: A> A> Асинхронные операции могут выполняться (и\или завершаться) в другом потоке (потоке IOCP, потоке тред пула или явно создаваемом потоке при старте асинхронной операции). A> При кооперативной многозадачности со stackfull coroutines работа выполняется в одном системном потоке, т.е. этот поток должен существовать постоянно и как-то уметь "ждать" окончания асинхронных операций из первого пункта. A>A>Вопрос: как реализовано это "ожидание"? Гоняем холостой цикл с проверками "а не завершился ли кто"? Ждем на каком-нибудь системном объекте синхронизации (системном событии, например)? Как-то еще?
А тут факт наличия stackful coroutine ортогонален, так как трансформация кода полностью локальная.
"цикл с проверками" если и присутствует, то он был и до добавления корутин, например это может быть UI Loop, а-ля:
Здравствуйте, gandjustas, Вы писали:
G>Ну давай покажи какие realworld проблемы решает spirit. На проверку окажется что 90% уже решены в современных платформах, а еще 10% закрываются специальными средствами. G>Это кстати не только spirit касается, но и других "универсальных всемогутеров".
Вообще то spirit хорош не столько возможностью легко задавать прямо в коде сложные грамматики, сколько своим быстродействием.
Можем провести простейший тест. Допустим есть обычный текстовый файл. Он состоит из 100 строк. Каждая строка состоит из 100 000 (вполне нормальная ситуация для обработки экспериментальных данных) int'ов, разделённых запятыми. Задачка: прочитать файл, посчитать среднее по каждой строке и допустим вывести в отсортированном виде в консоль (это чтобы проконтролировать результат работы — тогда можно exe обменяться, а не напрягаться с построением чужих библиотек). С помощью Boost'a я напишу решение всей задачки ровно в 5 строк. И при этом оно будет думаю где-то раз в 10 быстрее твоего, сделанного с помощью стандартной библиотеки .Net.
G>Ты опять повторяешь заблуждение что решение на C++ лучше. Оно не лучше потому что: G>1) тормозит G>2) не масштабируется
Тебе осталось доказать эти два тезиса. Потому как ты их тут уже не раз повторяешь, но пока не привёл ни единого аргумента в из защиту. Показывай давай конкретно что там тормозит и где. )))
G>С чего ты взял? perceived performance от async IO растет, а распараллеливание IO без блокировки потоков делает софт быстрее\улучшает масштабируемость.
Ага. Только await/async для этого не требуется. )))
Здравствуйте, Ikemefula, Вы писали:
I>Если по f ты подразумеваешь асинхронный мутекс, то это будет правильный код.
Ага, как и в C++.
Ну в общем ты сам видишь, что если мы пишем кривой асинхронный код, то он будет одинаково глючить и в C++ и в C# — никакого волшебства нет. А если писать асинхронную часть корректно, то опять же никаких проблем не будет ни там, ни там. Единственная разница в том, что при реализации асинхронности через async/await в C# (а это кстати не единственный способ даже для .net'a), нам строго обязательно переписывать заново все библиотечные функции, использующиеся в стеке вызова. А в C++ можно спокойно использовать старые функции без такого переписывания.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Ikemefula, Вы писали:
I>>Если по f ты подразумеваешь асинхронный мутекс, то это будет правильный код.
_>Ага, как и в C++.
_>Ну в общем ты сам видишь, что если мы пишем кривой асинхронный код, то он будет одинаково глючить и в C++ и в C# — никакого волшебства нет.
_>А в C++ можно спокойно использовать старые функции без такого переписывания.
Два высказывания сильно противоречат. Вроде как убедились, что корректный синхронный код != корректный асинхронный.
То есть, если ты захочешь поменять поведение на асинхронное, то в большинстве случаев этот код превращется в некорректный асинхронный.
В кратце это так:
Begin()
Processing()
End()
Корректный синхронный ? Корректный. Переделываем в асинхронный твоей секретной техникой и получаем некорректный асинхронный.
Вопрос — кто будет переколбашивать код вот в такой
Здравствуйте, artelk, Вы писали:
A>Объяснил бы мне кто-нибудь вот что:
A> A> Асинхронные операции могут выполняться (и\или завершаться) в другом потоке (потоке IOCP, потоке тред пула или явно создаваемом потоке при старте асинхронной операции). A> При кооперативной многозадачности со stackfull coroutines работа выполняется в одном системном потоке, т.е. этот поток должен существовать постоянно и как-то уметь "ждать" окончания асинхронных операций из первого пункта. A>
A>Вопрос: как реализовано это "ожидание"? Гоняем холостой цикл с проверками "а не завершился ли кто"? Ждем на каком-нибудь системном объекте синхронизации (системном событии, например)? Как-то еще?
Как хочешь так и реализуй — полная свобода. Конкретно в моей реализации (20 строк простейшего кода, которые уже были в этой темке) было две функции:
Соответственно для многопоточной асинхронности в обычном GUI приложение реализуем что-то вроде:
Post2UI(void* coro) {PostMessage(coro);}
...
OnAsync(event_data* coro) {CallFromUI(coro);}//обработчик в классе приложения
А для консольного приложения без системных очередей можем сделать например так:
queue<void*> coro_queue;//предположим что это lock-free
Post2UI(void* coro) {coro_queue.push(coro);}
...
while(!coro_queue.empty) {CallFromUI(coro_queue.front()); coro_queue.pop();}//строчка где-то в глубине основного цикла вычислений
Ну а для серверного приложения работающего через асинхронный io вообще просто — там Post2UI в принципе не нужен, т.к. C++ библиотеки асинхронного UI уже сами делают всё что нужно (вызывают callback'и из нужных потоков):
StartAsyncOp(..., [=]{CallFromUI(__coro);});//последний параметр - callback, который будет вызван из нужного потока
Ничего более не требуется. Причём никто не мешает совместить это например с каким-то из вариантов выше.
И это только в рамках простейшей реализации, возникшей в результате форумного спора. А если серьёзно заняться этим вопросом, то там ещё столько всего модного можно наделать при таком уровне свободы...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, artelk, Вы писали:
A>>Вопрос: как реализовано это "ожидание"? Гоняем холостой цикл с проверками "а не завершился ли кто"? Ждем на каком-нибудь системном объекте синхронизации (системном событии, например)? Как-то еще?
EP>А тут факт наличия stackful coroutine ортогонален, так как трансформация кода полностью локальная. EP>"цикл с проверками" если и присутствует, то он был и до добавления корутин, например это может быть UI Loop, а-ля: EP>
Т.е. из другого потока "шлется" "message", чтобы разбудить UI-поток и заставить его выполнить продолжение? Или продолжение будет выполнено не сразу, а только после того, как "хоть какой-то" message придет?
А что если UI Loop-а нет?
EP>[/ccode] Либо как в моем примере блокирующая очередь с active messages:
<...>
На сколько удалось разобрать (давно с++ не трогал), в качестве "оповещения о завершении" из других потоков у тебя делается добавление задания в main_tasks, которое будет выполняться в main потоке. "Задание" заключается в том, чтобы "перескочить" в место, откуда делался await, так? EP>
Так я нигде и не говорю о каком-то волшебном автоматическом превращение старого синхронного приложения в асинхронное. Речь именно о библиотеках. Т.е. мы пишем новое асинхронное приложение с нуля, но при этом можем спокойно использовать в нём синхронные библиотечные функции. Я думаю не нужно уточнять какое соотношение между написанными за всё время классическими библиотеками и переписанными под await/async? )
Здравствуйте, artelk, Вы писали:
EP>>А тут факт наличия stackful coroutine ортогонален, так как трансформация кода полностью локальная. EP>>"цикл с проверками" если и присутствует, то он был и до добавления корутин, например это может быть UI Loop, а-ля: EP>>
A>Т.е. из другого потока "шлется" "message", чтобы разбудить UI-поток и заставить его выполнить продолжение? Или продолжение будет выполнено не сразу, а только после того, как "хоть какой-то" message придет? A>А что если UI Loop-а нет?
Вот у нас есть async_something — функция, которая принимает callback. Когда async_something выполнит свою работу (например считает данные из сокета), она вызовет наш callback:
async_something([]
{
// Will be called on completion.
});
Как произойдёт вызов callback'а? Есть разные варианты: например код который получит событие, сразу вызовет наш callback, или другой вариант — он пошлёт сообщение в очередь (например через PostMessage), а тот кто делает pop из очереди запустит callback.
Заметь, callback нужно вызывать независимо от того есть ли у нас корутины или нет.
EP>>[/ccode] Либо как в моем примере блокирующая очередь с active messages: A><...> A>На сколько удалось разобрать (давно с++ не трогал), в качестве "оповещения о завершении" из других потоков у тебя делается добавление задания в main_tasks, которое будет выполняться в main потоке. "Задание" заключается в том, чтобы "перескочить" в место, откуда делался await, так?
Да, абсолютно верно. В данном примере await помогает заменить код вида:
async([] // запустить в thread pool
{
int result = do_job_in_pool();
return result;
}).then([](int res)
{
main_tasks.push([]
{
cout << "Job completed, result is: " << res << end;
});
});
на:
int res = await async([] // запустить в thread pool
{
int result = do_job_in_pool();
return result;
});
cout << "Job completed, result is: " << res << end;
Причём это простейший пример, если добавляются циклы, условия — без await/yield/coroutine/etc, код становится запутанным и утомительным.
A>
A> void pop(T &result)
A> {
A> unique_lock<mutex> u(m);
A> c.wait(u, [&]{return !q.empty();} );
A> result = move_if_noexcept(q.front());
A> q.pop();
A> }
A>
A>В "pop" делается блокировка. Т.е. это случай "ждем на каком-нибудь системном объекте синхронизации", так?
Да, всё правильно — объект синхронизации здесь это mutex + condition_variable. Грубо говоря поток засыпает пока данные не придут в очередь.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, gandjustas, Вы писали:
G>>Ну давай покажи какие realworld проблемы решает spirit. На проверку окажется что 90% уже решены в современных платформах, а еще 10% закрываются специальными средствами. G>>Это кстати не только spirit касается, но и других "универсальных всемогутеров".
_>Вообще то spirit хорош не столько возможностью легко задавать прямо в коде сложные грамматики, сколько своим быстродействием.
_>Можем провести простейший тест. Допустим есть обычный текстовый файл. Он состоит из 100 строк. Каждая строка состоит из 100 000 (вполне нормальная ситуация для обработки экспериментальных данных) int'ов, разделённых запятыми. Задачка: прочитать файл, посчитать среднее по каждой строке и допустим вывести в отсортированном виде в консоль (это чтобы проконтролировать результат работы — тогда можно exe обменяться, а не напрягаться с построением чужих библиотек). С помощью Boost'a я напишу решение всей задачки ровно в 5 строк. И при этом оно будет думаю где-то раз в 10 быстрее твоего, сделанного с помощью стандартной библиотеки .Net.
Но что-то мне кажется что на такой задаче ввод-вывод будет занимать большую часть времени.
Кстати это верно для любого простого парсинга. А для сложного, Spitit неочень подойдет.
Присылай exe, проверим.
G>>Ты опять повторяешь заблуждение что решение на C++ лучше. Оно не лучше потому что: G>>1) тормозит G>>2) не масштабируется
_>Тебе осталось доказать эти два тезиса. Потому как ты их тут уже не раз повторяешь, но пока не привёл ни единого аргумента в из защиту. Показывай давай конкретно что там тормозит и где. )))
Конкретно тормозит маршалинг каждого вызова через UI. Это для простых фоновых операций подойдет, а для сложных — будет тормозить.
Да и банально запусти синхронный и "асинхронный" вариант и сравни.
В C# достаточно написать ConfigureAwait(false) и продолжение будет запускаться по месту, а не в контексте синхронизации. А в C++ у тебя корутины привязаны к потоку, в котором создаются.
Что касается масштабирования, то все просто — все корутины работают в одном потоке, по факту получится очередь с одним читателем, даже если у тебя в системе десятки ядер.
G>>С чего ты взял? perceived performance от async IO растет, а распараллеливание IO без блокировки потоков делает софт быстрее\улучшает масштабируемость.
_>Ага. Только await/async для этого не требуется. )))
Конечно не требуется await/async "всего лишь" способ удобно записать асинхронные вызовы, которые в .NET существуют 10 лет.
Но именно при появлении await/async
Здравствуйте, gandjustas, Вы писали:
G>Что касается масштабирования, то все просто — все корутины работают в одном потоке, по факту получится очередь с одним читателем, даже если у тебя в системе десятки ядер.
Почему все в одном? Запускай несколько потоков, каждый со своими корутинами.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, gandjustas, Вы писали:
G>>Что касается масштабирования, то все просто — все корутины работают в одном потоке, по факту получится очередь с одним читателем, даже если у тебя в системе десятки ядер.
EP>Почему все в одном? Запускай несколько потоков, каждый со своими корутинами.
Это априори более медленный вариант, чем пул потков с продолжениями.
Кроме того такой вариант гораздо сложнее реализовать.
Здравствуйте, gandjustas, Вы писали:
EP>>Почему все в одном? Запускай несколько потоков, каждый со своими корутинами. G>Это априори более медленный вариант, чем пул потков с продолжениями.
Если у тебя .then выполняется в другом потоке то смысла в await мало. Например для UI продолжения должны запускаться в UI Thread
G>Кроме того такой вариант гораздо сложнее реализовать.
Нет. Например при использовании Boost.Asio иметь свой io_service per thread — это нормально, и просто Наоборот, использование одного io_serive на несколько потоков считается более сложным.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, gandjustas, Вы писали:
EP>>>Почему все в одном? Запускай несколько потоков, каждый со своими корутинами. G>>Это априори более медленный вариант, чем пул потков с продолжениями.
EP>Если у тебя .then выполняется в другом потоке то смысла в await мало. Например для UI продолжения должны запускаться в UI Thread
Те продолжения, которые должны в UI запускаться там и будут запускаться, но если ты например делаешь копирование из одного потока в другой в асинхронном цикле, то какой смысл маршалить в UI продолжения?
Как раз более половины продолжений в прикладной программе не нуждаются в выполнении в UI. А в библиотеке все 100%.
G>>Кроме того такой вариант гораздо сложнее реализовать. EP>Нет. Например при использовании Boost.Asio иметь свой io_service per thread — это нормально, и просто Наоборот, использование одного io_serive на несколько потоков считается более сложным.
Ну это только особенности boost. А если смотреть картину в целом, то для маршалинга всех продолжений в один поток надо как-то информацию протаскивать в продолжения. В boot это делается за счет stackfull корутин, что повышает требования к памяти, ибо хранить стек, не тоже само что хранить замыкание с 3-5 значениями.
Здравствуйте, gandjustas, Вы писали:
G>>>Кроме того такой вариант гораздо сложнее реализовать. EP>>Нет. Например при использовании Boost.Asio иметь свой io_service per thread — это нормально, и просто Наоборот, использование одного io_serive на несколько потоков считается более сложным. G>Ну это только особенности boost. А если смотреть картину в целом, то для маршалинга всех продолжений в один поток надо как-то информацию протаскивать в продолжения.
Да не надо ничего протаскивать — там разные io_service'ы на каждый поток
G>В boot это делается за счет stackfull корутин, что повышает требования к памяти, ибо хранить стек, не тоже само что хранить замыкание с 3-5 значениями.
Там такая схема рекомендуется даже без использования корутин, хватит фантазировать.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, gandjustas, Вы писали:
G>>>>Кроме того такой вариант гораздо сложнее реализовать. EP>>>Нет. Например при использовании Boost.Asio иметь свой io_service per thread — это нормально, и просто Наоборот, использование одного io_serive на несколько потоков считается более сложным. G>>Ну это только особенности boost. А если смотреть картину в целом, то для маршалинга всех продолжений в один поток надо как-то информацию протаскивать в продолжения.
EP>Да не надо ничего протаскивать — там разные io_service'ы на каждый поток
io_service это очередь и message pump, при вызове функции идет привязка в какой поток маршалить завершение. Думаешь такая привязка бесплатная?
G>>В boot это делается за счет stackfull корутин, что повышает требования к памяти, ибо хранить стек, не тоже само что хранить замыкание с 3-5 значениями. EP>Там такая схема рекомендуется даже без использования корутин, хватит фантазировать.
Там это где? В бусте? Конечно, потому что для нормальной работы C++ нужен стек.
В языках с gc нет такого ограничения, поэтому есть возможность делать более эффетивную асинхронность и агрессивно переписывать код.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Ikemefula, Вы писали:
_>Так я нигде и не говорю о каком-то волшебном автоматическом превращение старого синхронного приложения в асинхронное. Речь именно о библиотеках. Т.е. мы пишем новое асинхронное приложение с нуля, но при этом можем спокойно использовать в нём синхронные библиотечные функции. Я думаю не нужно уточнять какое соотношение между написанными за всё время классическими библиотеками и переписанными под await/async? )
Что бы давать гарантию, код в такой библиотеке придется писать через continuation и прочие приседения, и только в таких вот местах ты получишь профит от бустовых короутин.
Т.е. в C# нужно будет переделать код на await/async, а в c++ переколбасить на чтото вроде f(begin, processing, end)
Ты не правильно прочитал условие задачки. Я поправлю в рамках моего знания C# (если что не так, то скажи — поправим и протестируем заново):
foreach(var a in File.ReadAllLines("Numbers.txt")
.Select(l => l.Split(',').Select(int.Parse).Average(v => v))
.OrderBy(i => i))
Console.WriteLine(a);
G>Но что-то мне кажется что на такой задаче ввод-вывод будет занимать большую часть времени.
Ну так тогда значит и время исполнения будет не сильно отличаться...
G>Кстати это верно для любого простого парсинга. А для сложного, Spitit неочень подойдет.
Пока что я вижу что это не верно даже для такого. )))
G>Присылай exe, проверим.
И так результаты:
C#: TotalMilliseconds : 3888,1258
C++: TotalMilliseconds : 280,8465
О, я даже немного ошибся в своём прогнозе... Не в 10 раз тормознее .net код, а в 14!
G>Конкретно тормозит маршалинг каждого вызова через UI. Это для простых фоновых операций подойдет, а для сложных — будет тормозить.
Кто такие "сложные" фоновые операции? Если ты снова про серверный вариант, то там никто и не собирается использовать цикл сообщений — там рулит библиотека IO.
G>Что касается масштабирования, то все просто — все корутины работают в одном потоке, по факту получится очередь с одним читателем, даже если у тебя в системе десятки ядер.
Ну так и запускаем много потоков и в каждом свой набор сопроцедур. Собственно Boost.Asio именно на такой сценарий и заточен (в смысле без сопроцедур, а просто с коллбэками)
G>Конечно не требуется await/async "всего лишь" способ удобно записать асинхронные вызовы, которые в .NET существуют 10 лет. G>Но именно при появлении await/async
О чём я и говорю. Пользы для быстродействия они не приносят (только некий сахар для программиста), а накладные расходы добавляют. Пусть и не особо страшные.
Здравствуйте, Ikemefula, Вы писали:
I>Что бы давать гарантию, код в такой библиотеке придется писать через continuation и прочие приседения, и только в таких вот местах ты получишь профит от бустовых короутин.
I>Т.е. в C# нужно будет переделать код на await/async, а в c++ переколбасить на чтото вроде f(begin, processing, end)
I>Итого — кде профит ?
Не, не так. Достаточно чтобы функции библиотеки использовали блокирующий код передающийся (как функция или класс) откуда-то снаружи, а не зашитый внутри самой функции. Т.е. например если функция получает в качестве параметра не имя файла, а ссылку на поток ввода. В таком случае можем легко использовать в асинхронном коде без всякого переписывания.
Здравствуйте, alex_public, Вы писали:
_>Не, не так. Достаточно чтобы функции библиотеки использовали блокирующий код передающийся (как функция или класс) откуда-то снаружи, а не зашитый внутри самой функции. Т.е. например если функция получает в качестве параметра не имя файла, а ссылку на поток ввода. В таком случае можем легко использовать в асинхронном коде без всякого переписывания.
function operation(stream)
{
var value = read(stream);
write(next(value), stream)
}
Поясни, чем тебе помог этот параметр ? Когда код станет асинхронным, цепочки будут работать как попало
I>function operation(stream)
I>{
I> var value = read(stream);
I> write(next(value), stream)
I>}
I>
I>Поясни, чем тебе помог этот параметр ? Когда код станет асинхронным, цепочки будут работать как попало
Тем, что тогда можно написать так:
async_code(
operation(async_stream);
)
А что за цепочки то, ты вообще про что? Поясню на всякий случай как работает этот код:
1. вход в блок асинхронного кода
2. вызов operation
3. вызов read
4. вызов read члена async_stream, который инициализирует начало асинхронного чтения (через соответствующие функции или в другом потоке) и сразу после этого инициирует выход из сопроцедуры
5. управление передаётся на код следующий за блоком асинхронного кода
...
5. данные прочитались, а наш поток освободился и инициировал вход в сопроцедуру
6. функция read возвращает прочитанное значение
7. продолжение нормального синхронного исполнения функции operation в изначальном потоке, с прочитанными асинхронно данными.
...