Информация об изменениях

Сообщение Re[42]: cppcms от 26.09.2014 16:41

Изменено 26.09.2014 17:33 artelk

Здравствуйте, alex_public, Вы писали:

_>Здравствуйте, artelk, Вы писали:


_>Я в предыдущем сообщение несколько сумбурно всё написал, смешав совсем разные вещи. Сейчас попробую систематизировать.


_>И так, у нас речь о выполнение неких задач параллельно (действительно параллельно или скажем "поочерёдно").

Речь о большом множестве (во много (например, в тысячи раз) больше, чем количество ядер) одновременных задач, каждая из которых, помимо небольшой нагрузки на CPU, выполняет асинхронные IO операции. Задачи добавляются в течение работы приложения динамически, если это имеет значение. Вобщем у нас некий типичный сервис, от которого требуется высокая масштабируемость.
Внутри одной конкретной задачи никакого распараллеливания не нужно — у нас и так другие ядра будут почти всегда заняты.

_>Я бы разделил всю эту большую область на две отдельные части:


_>1. Когда не требуется небольшое число одновременных задач.

Когда не требуется большое число одновременных задач?

_>В данном случае на мой взгляд самым оптимальным является использование системных потоков (и соответственно блокирующего IO). Возможно кто-то не согласится (готов подискутировать и даже потестировать), но я не вижу ни единого аргумента в пользу других решений.


Если одновременных задач меньше, чем ядер, то да, создаем рабочих потоков по необходимости (переиспользуя их для вновь входящих запросов), используем блокирующий IO и радуемся жизни. Более того, если в пределах задач время IO операций сильно больше времени CPU, то с блокирующим IO можно хорошо жить, даже если число задач в десятки или даже в 100 раз больше, чем число ядер. Правде есть риск, что в какие-то моменты времени у нас будет возникать одновременно неспящих потоков больше, чем число ядер, что заставит систему их по кругу переключать. Если сильно больше — то на переключение между потоками будет тратиться заметное количество времени (ОС не знает, что эти переключения делать не нужно).

_>Но не всё так просто. В случае использования системных потоков и отдельных подсистем (типа UI) возникает проблема возврата данных в некий выделенный поток. У этой проблемы есть много разных решений с разной сложностью и удобством. Например в .net эту проблему довольно эффективно решает await. Существует полностью аналогичная реализация этого в C++ на базе Boost.Coroutine — обсуждалось подробно на этом форуме год назад. Кстати, лично мне эта техника не нравится (хотя именно я и продемонстрировал тогда пример реализации), т.к. на мой взгляд она вызывает путаницу в коде (канонический пример был с запуском скачивания из сети внутри обработчика кнопок и последующим выводом результата скачивания). Лично мне больше нравится модель акторов (причём по схеме 1 актор=1 системный поток). Естественно есть и ещё варианты (например продолжения и т.п.).

Без поддержки языка работать с продолжениями неудобно — будет лапша. Await внутри реализован через корутины, но это детали реализации. Вполне могло быть реализовано через продолжения, как в F# (Asynchronous Workflows). Интересует не "полностью аналогичная реализация", а аналогичная возможность — чтоб лапши небыло, но с масштабируемостью из коробки.
А чем с акторами проще?

_>2. Когда требуется небольшое число одновременных задач.

Когда требуется большое число одновременных задач?

_>В данном случае очевидно надо переходить к асинхронному IO. А вот механизм исполнения параллельных задач может быть очень разный: можно работать непосредственно на базе асинхронных колбэков (используя библиотеки типа libevent, libev или вообще руками), а можно использовать какую-то разновидность зелёных потоков, работающих поверх пула системных. Причём если говорить о зелёных потоках, то у них есть множество очень разных реализаций:

_> — Это могут быть сопрограммы поверх пула системных. Причём опять же разделяются на: stackless (как в .net await или в примерах boost.asio) и stackfull (boost.coroutine). Насколько я понял именно применение последних в данном контексте тебя и интересует. Так вот фокус в том, что в данном случае они "просто работают". ))) Т.е. какой-то дополнительный код потребовался именно для реализации техники "возврата в спец. поток", а для просто реализации зелёных потоков поверх пула системных ничего дописывать и не надо — оно просто работает. Как собственно Евгений и продемонстрировал. Вот разве что реализацию семафора дописать.
Семафор с асинхронным wait-ом я ради красного словца привел (кстати, это Рихтер придумал и продал патент Майкрософту)) ).
Еще раз, меня в этой ветке интересует, чтоб масштабируемость по ядрам была и чтоб при этом код в лапшу не превращался. И да, я имею ввиду сервис, не UI.

_>а для просто реализации зелёных потоков поверх пула системных ничего дописывать и не надо — оно просто работает

Покажи?

_>- это могут быть акторы (уже более сложные, работающие поверх пула системных потоков). Тут конечно самый известный Эрланг, но и в C++ с этим не плохо. Мне например нравится библиотечка Theron.

Имхо, акторы хороши, когда модель приложения в виде асинхронно общающихся объектов, посылающих друг другу сообщения, является естественной для решаемой задачи. Неясно, нафиг они нужны для вышеупомянутого сервиса и для UI с кнопкой, по которой что-то асинхронно выкачивается. Можешь объяснить?

_>- встроенные в некоторые языки специфические реализации многозадачности

_>- всякие велосипедные реализации именно зелёных потоков в чистом виде.
Re[42]: cppcms
Здравствуйте, alex_public, Вы писали:

_>Здравствуйте, artelk, Вы писали:


_>Я в предыдущем сообщение несколько сумбурно всё написал, смешав совсем разные вещи. Сейчас попробую систематизировать.


_>И так, у нас речь о выполнение неких задач параллельно (действительно параллельно или скажем "поочерёдно").

Речь о большом множестве (во много (например, в тысячи раз) больше, чем количество ядер) одновременных задач, каждая из которых, помимо небольшой нагрузки на CPU, выполняет асинхронные IO операции. Задачи добавляются в течение работы приложения динамически, если это имеет значение. Вобщем у нас некий типичный сервис, от которого требуется высокая масштабируемость.
Внутри одной конкретной задачи никакого распараллеливания не нужно — у нас и так другие ядра будут почти всегда заняты.

_>Я бы разделил всю эту большую область на две отдельные части:


_>1. Когда не требуется небольшое число одновременных задач.

Когда не требуется большое число одновременных задач?

_>В данном случае на мой взгляд самым оптимальным является использование системных потоков (и соответственно блокирующего IO). Возможно кто-то не согласится (готов подискутировать и даже потестировать), но я не вижу ни единого аргумента в пользу других решений.


Если одновременных задач меньше, чем ядер, то да, создаем рабочих потоков по необходимости (переиспользуя их для вновь входящих запросов), используем блокирующий IO и радуемся жизни. Более того, если в пределах задач время IO операций сильно больше времени CPU, то с блокирующим IO можно хорошо жить, даже если число задач в десятки или даже в 100 раз больше, чем число ядер. Правде есть риск, что в какие-то моменты времени у нас будет возникать одновременно неспящих потоков больше, чем число ядер, что заставит систему их по кругу переключать. Если сильно больше — то на переключение между потоками будет тратиться заметное количество времени (ОС не знает, что эти переключения делать не нужно).

_>Но не всё так просто. В случае использования системных потоков и отдельных подсистем (типа UI) возникает проблема возврата данных в некий выделенный поток. У этой проблемы есть много разных решений с разной сложностью и удобством. Например в .net эту проблему довольно эффективно решает await. Существует полностью аналогичная реализация этого в C++ на базе Boost.Coroutine — обсуждалось подробно на этом форуме год назад. Кстати, лично мне эта техника не нравится (хотя именно я и продемонстрировал тогда пример реализации), т.к. на мой взгляд она вызывает путаницу в коде (канонический пример был с запуском скачивания из сети внутри обработчика кнопок и последующим выводом результата скачивания). Лично мне больше нравится модель акторов (причём по схеме 1 актор=1 системный поток). Естественно есть и ещё варианты (например продолжения и т.п.).

Без поддержки языка работать с продолжениями неудобно — будет лапша. Await внутри реализован через корутины, но это детали реализации. Вполне могло быть реализовано через продолжения, как в F# (Asynchronous Workflows). Интересует не "полностью аналогичная реализация", а аналогичная возможность — чтоб лапши небыло, но с масштабируемостью из коробки.
А чем с акторами проще?

_>2. Когда требуется небольшое число одновременных задач.

Когда требуется большое число одновременных задач?

_>В данном случае очевидно надо переходить к асинхронному IO. А вот механизм исполнения параллельных задач может быть очень разный: можно работать непосредственно на базе асинхронных колбэков (используя библиотеки типа libevent, libev или вообще руками), а можно использовать какую-то разновидность зелёных потоков, работающих поверх пула системных. Причём если говорить о зелёных потоках, то у них есть множество очень разных реализаций:

_> — Это могут быть сопрограммы поверх пула системных. Причём опять же разделяются на: stackless (как в .net await или в примерах boost.asio) и stackfull (boost.coroutine). Насколько я понял именно применение последних в данном контексте тебя и интересует. Так вот фокус в том, что в данном случае они "просто работают". ))) Т.е. какой-то дополнительный код потребовался именно для реализации техники "возврата в спец. поток", а для просто реализации зелёных потоков поверх пула системных ничего дописывать и не надо — оно просто работает. Как собственно Евгений и продемонстрировал. Вот разве что реализацию семафора дописать.
Семафор с асинхронным wait-ом я ради красного словца привел (кстати, это Рихтер придумал и продал патент Майкрософту)) ).
Еще раз, меня в этой ветке интересует, чтоб масштабируемость по ядрам была и чтоб при этом код в лапшу не превращался. И да, я имею ввиду сервис, не UI.

_>а для просто реализации зелёных потоков поверх пула системных ничего дописывать и не надо — оно просто работает

Покажи?

_>- это могут быть акторы (уже более сложные, работающие поверх пула системных потоков). Тут конечно самый известный Эрланг, но и в C++ с этим не плохо. Мне например нравится библиотечка Theron.

Имхо, акторы хороши, когда модель приложения в виде асинхронно общающихся объектов, посылающих друг другу сообщения, является естественной для решаемой задачи. Неясно, нафиг они нужны для вышеупомянутого сервиса и для UI с кнопкой, по которой что-то асинхронно выкачивается. Можешь объяснить?

_>- встроенные в некоторые языки специфические реализации многозадачности

_>- всякие велосипедные реализации именно зелёных потоков в чистом виде.