Russian Qt Forum

Qt => Общие вопросы => Тема начата: kramer3d от Февраль 24, 2010, 09:18



Название: Нагрузка на поток
Отправлено: kramer3d от Февраль 24, 2010, 09:18
Всем привет!

Есть многопоточный сервер, в главном потоке сидит наследник QTcpServer'а и слушает порт, а принятые соединения раздает рабочим потокам, которые сам и создает при необходимости. Один рабочий поток может обслуживать несколько соединений. Характер вычислений, производимых в потоке для какого-либо соединения совершенно непредсказуем. Это может быть простой обмен сообщениями, может быть и запрос в базу, может быть обращение к ядру сервера за какими-то system-wide данными, могут быть вычисления, передача файлов, соединения могут быть защищенными SSL, короче, характер нагрузки на поток количественному анализу не поддается, т.е. нет прямой зависимости от количества подключений.

Вопрос в следующем: есть ли способ оценить нагрузку на поток (т.е. эффективность его Event-Loop'a) средствами Qt, и, если эта нагрузка превысит критический уровень, создать новый?

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


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 24, 2010, 10:49
А для чего было идти по пути Apache и сейчас решать его проблемы, о которых еще Владимир Ильич Ленин кажется писал?  :)
Почему, пропустили мимо глаз, то что в последнее время частенько используют связку nginx + Apache? Почему не разделить сервер на фронтэнд и бакэнд? Где фронтэнд будет заниматься только отправкой/получением данных для всех клиентов, а готовить эти данные будет бакэнд.
Не обязательно это все разделять физически, это могут быть два объект общающиеся через сигналы.
Думаю этот подход решит многие проблемы.


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 24, 2010, 11:35
Вопрос в следующем: есть ли способ оценить нагрузку на поток (т.е. эффективность его Event-Loop'a) средствами Qt, и, если эта нагрузка превысит критический уровень, создать новый?
такого способа нет, насколько я знаю.
можно, конечно, извратиться и сделать примерно так: задать функторам "вес" в относительных единицах (например, обмен сообщениями - 1, соединение с бд - 2, выборка данных - 5, запись в бд - 7, ...) и суммировать значения веса по ходу выполнения...даже звучит корявенько...

Держать каждое соединение в отдельном потоке <snip>
это вообще неэффективно...


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 24, 2010, 11:37
А для чего было идти по пути Apache и сейчас решать его проблемы, о которых еще Владимир Ильич Ленин кажется писал?  :)
Почему, пропустили мимо глаз, то что в последнее время частенько используют связку nginx + Apache? Почему не разделить сервер на фронтэнд и бакэнд? Где фронтэнд будет заниматься только отправкой/получением данных для всех клиентов, а готовить эти данные будет бакэнд.
Не обязательно это все разделять физически, это могут быть два объект общающиеся через сигналы.
Думаю этот подход решит многие проблемы.

будет такая же проблема с распределением нагрузки на бэкэнд, нет?


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 24, 2010, 12:04
будет такая же проблема с распределением нагрузки на бэкэнд, нет?
Случай, где на каждое соединение используется отдельная нить не рассматриваем совсем из-за непомерных расходов ресурсов.
Случай, где несколько клиентов обслуживаются в одной ните. При формировании ответа для одного клиента, останавливаются обслуживание всех остальные клиентов в данной ните. Даже если для большинства клиентов данные уже сформированы и их нужно просто отправлять.
В случае с разделением, получаем следующее. Фронтэнд получает данные от клиента в буфер, если пакет пришел полностью, он передается на обслуживание бэкэнду и фронтэнд о нем забывает (или продолжает принимать следующие данные от клиента). Бекенд принимает решение, запустить другую нить для формирования ответа или при "легком" ответе сформировать его сразу и отдать его фронтэнду. Дальше уже фронтэнд имея данные для клиента небольшими частями начинает отправлять их клиенту.
Бэкэнд может подготовить данные для нескольких клиентов и фронтэнд непрерывно будет раздавать их с учетом возможности клиента (одни могут забирать данные медленно, другие быстро).
Потоки будут создаваться (браться из пула простаивающих) только для конкретных заданий. Передача не будет останавливаться при формировании ответов.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 24, 2010, 12:36
Цитировать
В случае с разделением, получаем следующее. Фронтэнд получает данные от клиента в буфер, если пакет пришел полностью, он передается на обслуживание бэкэнду и фронтэнд о нем забывает (или продолжает принимать следующие данные от клиента). Бекенд принимает решение, запустить другую нить для формирования ответа или при "легком" ответе сформировать его сразу и отдать его фронтэнду.
У меня как раз вот этот вариант, разница только в том, что решение о легковесности ответа принимает фронтэнд, а не бэкэнд, а точнее, обработчик данных внутри потока фронтэнда, чтобы не забивать очередь бэкэнда, которому и так есть чем заняться. При этом я хочу сделать фронтэнд многопоточным с однородными потоками. Так что мне нужен критерий создания нового потока фронтэнда. Пока я придумал вот что - в EventLoop'е потока фронтэнда создаю таймер, который постит кастомные события с таймстампом самому себе через равные промежутки времени. Обработчик эти события отлавливает, замеряет время, которое событие простояло в очереди, усредняет/сглаживает его и пишет в член объекта потока. Это получается такая усредненная занятость. Пул менеджер потоки сортирует по этому значению, и по запросу выдает ссылку на поток с наименьшим значением, upd: ну, да, и если потока со значением меньше критического нету, создает новый и выдает ссылку на него. По-моему, довольно кривенько, и оверхед присутствует (вроде небольшой, хотя кто знает), но альтернативы пока не вижу. Пожалуйста, советуйте. :)


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 24, 2010, 12:49
Т.е. ты успеваешь формировать данные для клиентов, но не успеваешь их отдавать?
Как происходит отдача? Ты же не пытаешься пропихнуть все данные одному клиенту разом?
Описывай?  ;)


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 24, 2010, 13:27
Внутри потока фронтенда живет список клиентов (объектов, инкапсулирующих сокеты, по сути). Один клиент, при желании, может иметь несколько подключений (тянуть файл по одному и работать с базой по другому). Клиент отвечает за свои подключения, отчитывается перед бэк-эндом (ядром сервака), пишет в логи и мало ли чего еще. Полученные данные он десериализует и вызывает для них зависящую от их типа функцию-обработчик, которая решает, что дальше с ними делать. Естественно, у него есть и очередь данных на отправку и обработчик событий, который в эту очередь ставит данные, пришедшие от бэкэнда или от других клиентов. По отправке данных (или их верификации, опционально) клиент сообщает их отправителю, что все дошло и все в порядке, и отправитель их прибивает (пока не берем в расчет кэширование, там отдельная песня). Сам по себе клиент - наследник QObject, живет в потоке и участвует в очереди. Кроме того, у него есть куча членов, которые тоже наследники QObject (сокеты, интерфейс к логгеру и т.п.). Так вот я и думаю, не до хрена ли это будет? В том смысле, что если вертеть всю эту банду в одном потоке, при тысяче клиентов уже возможны ощутимые тормоза из-за обработки очереди событий. Тысяча клиентов для этого проекта - это очень много, но я сейчас пока на этапе проектирования и программирования прототипов, и хочу предусмотреть возможность масштабирования в будущем.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 24, 2010, 14:00
Достоинство описанного метода как раз в том, что он может отдавать данные практически одновременно всем клиентам в одном потоке. При необходимости, есть возможность масштабирования, например по количеству ядер процессора.
Главное это отдавать данные частями, а не упираться в одного клиента и пихать ему весь блок целиком. С каждым клиентом ассоциируется выходной буфер (это абстракция, может быть буфер в памяти, а может быть файлом на диске), если в нем есть данные они должны быть отправлены. В цикле проверяем для каждого клиента, если данные есть, отправляем кусок (chunk) и переходим к следующему клиенту. Размер "куска" можно задать фиксированным или проверять количество свободных байт в буфере отправки tcp-стека.
Этот метод легко ложиться на архитектуру Qt-сокетов, т.к. они работают по принципу не блокируемых сокетов.


Название: Re: Нагрузка на поток
Отправлено: kuzulis от Февраль 24, 2010, 14:35
2 BRE,
это сродни "Теории конечных автоматов" ? :)
т.е. предлагаете нечто подобное автоматному программированиию?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 24, 2010, 14:44
Хммм. Поправь меня если я ошибаюсь. Насколько я знаю, буферизацией в данном случае занимается QIODevice, от которого отнаследован QTcpSocket. Т.е. данные из очереди на отправку сериализуются в сокет с помощью write() или QDataStream, сокет их буферизует, и уже сам решает, когда и какими кусками их отправлять. Отправив кусок, эмитит bytesWritten, который воткнут в слот моего объекта-клиента, где я проверяю, есть ли у сокета еще данные в буфере, и если нет, то сериализую туда следующий объект из очереди. Сокет, как я понимаю, работает асинхронно, в цикле событий потока. Так что я совсем не забочусь об отправке данных по частям параллельно всем клиентам - это должен за меня делать эвент-луп, который обходит все объекты, созданные в потоке, и говорит им, что делать. Сокет в момент вызова write() ничего никуда не пишет, а просто копирует данные в свой буфер. Когда метод, вызвавший write(), вернет управление циклу событий, и когда этот цикл дойдет до объекта сокета, вот тут-то кусок данных будет отправлен непосредственно на интерфейс, и проэмичен сигнал bytesWritten.
Цитировать
При необходимости, есть возможность масштабирования, например по количеству ядер процессора.
Это подразумевает выполнение в несколько потоков, так ведь? Пусть даже их количество фиксировано и равно числу ядер, но мне в любом случае необходимо будет при новом входящем подключении выяснить, какой их них наименее загружен, чтобы отдать это подключение именно ему. А как я раньше писал, в моем случае прямой корреляции между загрузкой потока и количеством обслуживаемых подключений нет. Хотя бы потому, что подключения могут быть шифрованными, что съедает немало процессорного времени, а могут не быть. Вот к тому и вопрос, как выяснить загрузку потока?
Какие есть мнения по поводу вот этого:
Цитировать
Пока я придумал вот что - в EventLoop'е потока фронтэнда создаю таймер, который постит кастомные события с таймстампом самому себе через равные промежутки времени. Обработчик эти события отлавливает, замеряет время, которое событие простояло в очереди, усредняет/сглаживает его и пишет в член объекта потока. Это получается такая усредненная занятость. Пул менеджер потоки сортирует по этому значению, и по запросу выдает ссылку на поток с наименьшим значением, upd: ну, да, и если потока со значением меньше критического нету, создает новый и выдает ссылку на него. По-моему, довольно кривенько, и оверхед присутствует (вроде небольшой, хотя кто знает), но альтернативы пока не вижу.
Какие тут могут быть подводные камни?


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 24, 2010, 15:13
Хммм. Поправь меня если я ошибаюсь. Насколько я знаю, буферизацией в данном случае занимается QIODevice, от которого отнаследован QTcpSocket....
Да Qt-сокет все кеширует и отправляет частями, только что делать если основной массе клиентов отдаются огромные файлы (образы DVD). Куда это будет кешироваться, что будет с памятью и свопом? Как будет распределяться нагрузка на канал связи? Первым клиентам уйдут огромные куски, остальные будут курить в стороне. А можно принудительно отдавать каждому клиенту по очереди блоками по 20-40 Кб.

Это подразумевает выполнение в несколько потоков, так ведь? Пусть даже их количество фиксировано и равно числу ядер, но мне в любом случае необходимо будет при новом входящем подключении выяснить, какой их них наименее загружен, чтобы отдать это подключение именно ему. А как я раньше писал, в моем случае прямой корреляции между загрузкой потока и количеством обслуживаемых подключений нет. Хотя бы потому, что подключения могут быть шифрованными, что съедает немало процессорного времени, а могут не быть. Вот к тому и вопрос, как выяснить загрузку потока?
Если отдавать равными блоками, то нагрузка на поток будет делиться на всех клиентов, для которых есть готовые данные.
Например, есть два потока фронтэнда, в одном подготовлены (ждут отправки) данные для 100 клиентов, во втором для 20 клиентов. Значит мы можем из первого потока перенести во второй 40 клиентов. Время отправки данных фиксировано, т.к. размер куска одинаковый.

Какие есть мнения по поводу вот этого:
...
Сейчас подумаю...  :)


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 24, 2010, 15:17
Плюс, можно назначать разные приоритеты на отдачу, одним клиентам отдавать по 100 кб за раз, а другим урезать до 20 кб.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 24, 2010, 15:44
Да Qt-сокет все кеширует и отправляет частями, только что делать если основной массе клиентов отдаются огромные файлы (образы DVD). Куда это будет кешироваться, что будет с памятью и свопом? Как будет распределяться нагрузка на канал связи? Первым клиентам уйдут огромные куски, остальные будут курить в стороне. А можно принудительно отдавать каждому клиенту по очереди блоками по 20-40 Кб.
Ааа. Теперь понял. Это предусмотрено. Есть специальный тип данных, отвечающий передачу файлов, у него специальная сериализация/десериализация, которая подсасывает файл кусками с диска, или также кусками пишет принятые данные на диск, попутно считая контрольные суммы. :) В бэкэнде есть диспетчетр файлов, который отправляет клиентам файлы и за этим всем следит.
Если отдавать равными блоками, то нагрузка на поток будет делиться на всех клиентов, для которых есть готовые данные.
Например, есть два потока фронтэнда, в одном подготовлены (ждут отправки) данные для 100 клиентов, во втором для 20 клиентов. Значит мы можем из первого потока перенести во второй 40 клиентов. Время отправки данных фиксировано, т.к. размер куска одинаковый.
Короче, я идею понял. Я в клиент/серверном программировании пока новичок, это первый большой проект. Буду думать, как это адаптировать под свои реалии. Получается, нужен еще один уровень абстракции над сокетом - блок данных. И тогда нагрузка на поток будет характеризоваться количеством блоков.
Сейчас подумаю...  :)
Спасибо! Вопрос безотносительно серверной архитектуры пока так и остается открытым. Т.е. как быть, если есть поток, производящий вычисления исходя из поступающих извне данных? Как этому потоку понять, что он загружен по самое не могу и больше данных принимать не может?


Название: Re: Нагрузка на поток
Отправлено: Igors от Февраль 24, 2010, 18:46
Пока я придумал вот что - в EventLoop'е потока фронтэнда создаю таймер, который постит кастомные события с таймстампом самому себе через равные промежутки времени. Обработчик эти события отлавливает, замеряет время, которое событие простояло в очереди, усредняет/сглаживает его и пишет в член объекта потока. Это получается такая усредненная занятость. Пул менеджер потоки сортирует по этому значению, и по запросу выдает ссылку на поток с наименьшим значением, upd: ну, да, и если потока со значением меньше критического нету, создает новый и выдает ссылку на него. По-моему, довольно кривенько, и оверхед присутствует (вроде небольшой, хотя кто знает), но альтернативы пока не вижу. Пожалуйста, советуйте. :)
Красиво но сложно  :) Почему не простенько: задачи принимаются и складываются в одну общую очередь. Каждая нитка берет следующую задачу из этой очереди как только закончит предыдущую. Число ниток просто фиксировано. Вероятно задачи могут быть зависимыми, напр задача "B" может требовать результатов задачи "A". Мне кажется не стоит решать это созданием новых ниток (напр. нитка не может принять "C" потому что она ждет "B", давайте создадим еще нитку). Лучше сохранять результаты/контекст а потом восстанавливать.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 24, 2010, 19:21
Красиво но сложно  :) Почему не простенько: задачи принимаются и складываются в одну общую очередь. Каждая нитка берет следующую задачу из этой очереди как только закончит предыдущую. Число ниток просто фиксировано. Вероятно задачи могут быть зависимыми, напр задача "B" может требовать результатов задачи "A". Мне кажется не стоит решать это созданием новых ниток (напр. нитка не может принять "C" потому что она ждет "B", давайте создадим еще нитку). Лучше сохранять результаты/контекст а потом восстанавливать.
Проблема в том, что задача асинхронная. Т.е. объекты в нитке управляются событиями, приходящими извне. Можно, конечно, выделить очередь событий в отдельную нитку, с которой рабочие потоки будут разбирать задания строго по одному (тут появляется оверхед с сериализацией доступа: получить мьютекс - это довольно дорогая операция в контексте мелких задач на полста умножений). Но в этом случае опять же встает проблема масштабируемости для потока, управляющего очередью заданий - что делать, если очередь событий в нем забилась окончательно, и задание в разы дольше дожидается "постановки на учет", чем собственно обрабатывается? Понятно, надо создать еще одну нитку с параллельной очередью заданий, и добавлять задания в ту, в которой их меньше. Вопрос в том, как узнать, что очередь событий потока заполнилась, и добавлять туда новые неэффективно? Понятно, я ищу сферического коня в вакууме, этакую идеальную парадигму масштабирования, но ведь Генри Форд нам завещал всегда желать большего. :)


Название: Re: Нагрузка на поток
Отправлено: Igors от Февраль 24, 2010, 20:16
Проблема в том, что задача асинхронная. Т.е. объекты в нитке управляются событиями, приходящими извне. Можно, конечно, выделить очередь событий в отдельную нитку, с которой рабочие потоки будут разбирать задания строго по одному (тут появляется оверхед с сериализацией доступа: получить мьютекс - это довольно дорогая операция в контексте мелких задач на полста умножений). Но в этом случае опять же встает проблема масштабируемости для потока, управляющего очередью заданий - что делать, если очередь событий в нем забилась окончательно, и задание в разы дольше дожидается "постановки на учет", чем собственно обрабатывается? Понятно, надо создать еще одну нитку с параллельной очередью заданий, и добавлять задания в ту, в которой их меньше. Вопрос в том, как узнать, что очередь событий потока заполнилась, и добавлять туда новые неэффективно? Понятно, я ищу сферического коня в вакууме, этакую идеальную парадигму масштабирования, но ведь Генри Форд нам завещал всегда желать большего. :)
Про Форда ничего не слышал, но Ваш поиск коня мне нравится  :) Я свое мнение не навязываю. но по-моему Вы преувеличиваете проблему, вероятно ее здесь вообще нет. Для простоты положим что на сервере аж один (!) процессор - а запросы поступают интенсивно. Что толку что Вы (допустим) распихали все по ниткам? Один процессор все равно будет тянуть как он сможет, больше задач - больше накладных расходов на синхронизацию. Разумно играть на приоритетах запросов/событий, напр. "сейчас мало задач в обработке, приемщик новых запросов имеет имеет высший приоритет". И наоборот "работы валом, пусть следующие запросы ждут" - это неизбежная ситуация когда "сервер перегружен запросами" - и стесняться этого нечего. Динамическое создание новых ниток эту проблему не решит.

BTW: "kramer3d" - это просто так или действительно "3d"?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 05:51
Совершенно согласен, проблему я действительно преувеличиваю, но нельзя сказать, что ее вовсе нет. Она не стоит передо мной прямо сейчас, но на будущее, мне кажется, я обязан предусмотреть возрастающую нагрузку на сервер. При расширении аппаратной базы (увеличении количества ядер, повышении пропускной способности интерфейсов, увеличении объема памяти) программа должна реагировать соответственно, а не сидеть в одном распределяющем потоке, который и будет "бутылочным горлышком", сдерживающим возросшую производительность сервера. Надо б наверное что-нибудь сильно узкоспециальное почитать на тему масштабируемости и распределенных серверов, а то я, очевидно, велосипед изобретаю. Но с другой стороны, практика показывает, что в таких узкоспециальных областях применяют узкоспециальные абстракции, которые под более-менее общую задачу хрен подгонишь, и всегда приходится использовать компромиссы, и тогда еще неизвестно, кому повезло. :) В смысле, вполне возможно, было бы более эффективно решать задачу "в лоб".

А 3D - это не просто так, я действительно 3D, т.е. кроме прочего занимаюсь разработкой прикладного ПО для нужд трехмерной графики. :)


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 08:20
Я свое мнение не навязываю. но по-моему Вы преувеличиваете проблему, вероятно ее здесь вообще нет.
ты очень неправ. как уже было замечено выше, простая "очередь задач" даже при небольшой нагрузке уткнётся в значительные накладные расходы на синхронизацию рабочих потоков. и чем больше будет потоков, обращающихся к этой очереди задач, тем больше будет мат.ожидаемое время простоя каждого из них - это нормально для смешных серверов типа "тридедфортунесервер", но недопустимо для серьёзных серверов с задачами масштабирования/распределения нагрузки.
и  kramer3d абсолютно прав в том, что пытается заранее предусмотреть возможности масштабирования, которые не потребуют модификаций кода.
эдаких "сейчас же всё работает? а прижмёт - подумаем" я уже насмотрелся - когда "прижмёт", он сначала будет возмущаться что это, мол, не его вина, потом начнёт переписывать каждое узкое место заново, снова тратить на это время и снова получать зарплату...


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 08:46
Спасибо! Вопрос безотносительно серверной архитектуры пока так и остается открытым. Т.е. как быть, если есть поток, производящий вычисления исходя из поступающих извне данных? Как этому потоку понять, что он загружен по самое не могу и больше данных принимать не может?
Факт занятости потока по самое не могу это все таки величина относительная от загрузки других потоков.
Я бы немного изменил твой вариант, примерно так:
Если QTimer запустить с интервалом равным 0, то такой таймер будет срабатывать на каждой итерации цикла обработки событий. В его обработчике можно смотреть сколько времени прошло с момента предыдущего входа в этот слот. И зная сколько времени выполняется цикл для каждого потока, принимать решение о загруженности.


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 08:56
если я правильно понял описание, главный поток сервера - самый ненагруженный и можно его озадачить ещё чем-то полезным...опять же, если правильно понял, он также занимается определением того, какому рабочему потоку следует  отдать задачу, основываясь на некоторой циферке, указывающей на загруженность потока (кстати, описанный ранее принцип определения рабочей нагрузки меня настораживает - если интервал опроса недостаточно маленький, поток, только что завершивший "тяжёлую" задачу и теперь простаивающий, мог ещё не успеть ответить на пинг - как будто он всё ещё нагружен, задача достанется другому (более отзывчивому) потоку и будет в очереди на исполнение ещё некоторое время, тогда как в действительности имеем один простаивающий поток; если интервал опроса минимален (или 0), на очередь событий потока, в котором крутится таймер, постоянно будет создаваться дополнительная нагрузка).

итак, идея следующая: запросы (в вашей терминологии "задачи"?) складываются в простую очередь (QQueue) в порядке их получения (полагаю, это довольно честно, если учесть, что обычно клиенты не стараются заддосить сервер, а это - отдельная тема), а каждый рабочий поток сигнализирует о завершении обработки вверенного ему запроса - кто раньше отметился, что свободен, тот и получает на обработку следующий запрос из очереди (первый, если учесть, что у нас схема FIFO), если таковой имеется. при получении запроса от клиента создаём рабочие потоки по принципу стандартного пула (нужен новый и общее кол-во меньше максимального? - создаём; иначе выходим и первый освободившийся поток в дальнейшем получит данный запрос на обработку).
мысли/комментарии?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 09:12
Факт занятости потока по самое не могу это все таки величина относительная от загрузки других потоков.
Я бы немного изменил твой вариант, примерно так:
Если QTimer запустить с интервалом равным 0, то такой таймер будет срабатывать на каждой итерации цикла обработки событий. В его обработчике можно смотреть сколько времени прошло с момента предыдущего входа в этот слот. И зная сколько времени выполняется цикл для каждого потока, принимать решение о загруженности.
Спасибо за идею. :) По-моему, это то же самое, что и было, но с одним минусом - такой подход создаст немаленький оверхед для пустых потоков или потоков с очень низкой активностью. А поскольку афинностью потоков я напрямую не управляю - этим занимается ОС, может оказаться, что два потока будут крутиться на одном ядре, и более слабо нагруженный будет отжирать ресурс у более сильно нагруженного просто на обработку событий таймера.
В моем случае объект таймера - это член класса потока (наследника QThread), т.е. он живет в родительском потоке и не создает дополнительной нагрузки на рабочий поток. Он всего лишь постит сообщения некоему объекту-обработчику, ссылка на который также хранится в классе, но который был создан динамически в теле run(), т.е. живет в рабочем потоке. Обработчик предельно легкий - померял время, фильтранул по Гауссу, например, записал куда надо и вышел.
 


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 09:44
если я правильно понял описание, главный поток сервера - самый ненагруженный и можно его озадачить ещё чем-то полезным...опять же, если правильно понял, он также занимается определением того, какому рабочему потоку следует  отдать задачу, основываясь на некоторой циферке, указывающей на загруженность потока (кстати, описанный ранее принцип определения рабочей нагрузки меня настораживает - если интервал опроса недостаточно маленький, поток, только что завершивший "тяжёлую" задачу и теперь простаивающий, мог ещё не успеть ответить на пинг - как будто он всё ещё нагружен, задача достанется другому (более отзывчивому) потоку и будет в очереди на исполнение ещё некоторое время, тогда как в действительности имеем один простаивающий поток; если интервал опроса минимален (или 0), на очередь событий потока, в котором крутится таймер, постоянно будет создаваться дополнительная нагрузка).
Да, согласен, такая проблема имеет место быть. Возможно, пересматривать время простоя в очереди нужно не по таймеру через равные интервалы времени, а по каким-то событиям, связанным с выполнением задачи. Т.е. ответственность за измерение нагрузки на поток можно возложить не на сам поток, а на объекты, которые в нем живут. Т.е. закончил тяжелую операцию - будь добр, запости событие в очередь, что нужно обновить данные о нагрузке. Тогда эти данные будут обновляться на каждой следующей итерации цикла событий. Т.е. закончилось исполнение, объект поставил сообщение об этом в очередь, обработчик сообщений запостил событие с таймстемпом, и уже на следующей итерации цикла событий информация о нагрузке обновилась. Это, по-моему, вполне приемлемо, с учетом того, что свежедобавленный объект будет обработан не в текущей итерации, а только в следующей, если я правильно понимаю логику работы Event Loop.
итак, идея следующая: запросы (в вашей терминологии "задачи"?) складываются в простую очередь (QQueue) в порядке их получения (полагаю, это довольно честно, если учесть, что обычно клиенты не стараются заддосить сервер, а это - отдельная тема), а каждый рабочий поток сигнализирует о завершении обработки вверенного ему запроса - кто раньше отметился, что свободен, тот и получает на обработку следующий запрос из очереди (первый, если учесть, что у нас схема FIFO), если таковой имеется. при получении запроса от клиента создаём рабочие потоки по принципу стандартного пула (нужен новый и общее кол-во меньше максимального? - создаём; иначе выходим и первый освободившийся поток в дальнейшем получит данный запрос на обработку).
мысли/комментарии?
Это, в принципе, уже было предложено Igors. см. мой ответ http://www.prog.org.ru/index.php?topic=12552.msg79842#msg79842
Но это так, чисто академически. А вообще, основная засада тут в том, что рабочие потоки - асинхронны, т.е. принимают не задачи в виде данных, которые можно обработать и забыть, а в виде QObject-ов, которые крутятся в их цикле событий. Причем нагрузка от этих объектов принципиально непредсказуема - у одного может быть единственный обработчик сообщений, а у другого - полста сигналов и слотов на QueuedConnection. Поэтому поток не может принимать решение о том, что пора взять еще один объект на обработку, основываясь на количестве обслуживаемых объектов. Вот я и пытаюсь выделить критерий, чтобы понять, насколько загружен Event Loop потока. В любом случае, большое спасибо за интерес, мысли и поддержку, так или иначе мне это очень помогает. :)


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 09:58
Спасибо за идею. :) По-моему, это то же самое, что и было, но с одним минусом - такой подход создаст немаленький оверхед для пустых потоков или потоков с очень низкой активностью. А поскольку афинностью потоков я напрямую не управляю - этим занимается ОС, может оказаться, что два потока будут крутиться на одном ядре, и более слабо нагруженный будет отжирать ресурс у более сильно нагруженного просто на обработку событий таймера.
IMHO, оверхед несомненно будет, но итерация цикла обработки событий операция тоже очень не дешевая, даже если поток простаивает. На его фоне прямой вызов одного слота, в котором, грубо говоря, выполняется операция вычитание, сильно ресурсы не отожрет. Это на фоне самой итерации цикла.



Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 10:06
IMHO, оверхед несомненно будет, но итерация цикла обработки событий операция тоже очень не дешевая, даже если поток простаивает. На его фоне прямой вызов одного слота, в котором, грубо говоря, выполняется операция вычитание, сильно ресурсы не отожрет. Это на фоне самой итерации цикла.
Трудно сказать вот так, навскидку. Но ведь можно потестить. :) Сейчас займусь. Надо бы профайлер поискать.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 10:13
Трудно сказать вот так, навскидку. Но ведь можно потестить. :) Сейчас займусь. Надо бы профайлер поискать.
Для интереса, посмотри в исходниках, сколько разных действий выполняется при одной итерации цикла. А ведь он так крутиться постоянно...  :)


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 10:26
Еще мысли...  :)
Для организации рабочих потоков я бы использовал QThreadPool/QRunnable или написал бы аналогичный механизм, при котором простаивающие нити запирались бы на мьютексе, и не занимали бы ресурсы даже на кручение eventloop.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 10:39
Еще мысли...  :)
Для организации рабочих потоков я бы использовал QThreadPool/QRunnable или написал бы аналогичный механизм, при котором простаивающие нити запирались бы на мьютексе, и не занимали бы ресурсы даже на кручение eventloop.
А тут опять та же проблема - QRunnable не подразумевает потоков с event loop'ом, т.е. такую функциональность мне придется реализовывать самому, и опять же из тред-пула нужно будет определять, насколько тот или иной event loop загружен, чтобы понять, можно ли туда добавлять объекты.
Я вот думаю основательно разобраться с QtConcurrent, но что-то мне подсказывает, что и он мне не поможет.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 10:42
А тут опять та же проблема - QRunnable не подразумевает потоков с event loop'ом, т.е. такую функциональность мне придется реализовывать самому, и опять же из тред-пула нужно будет определять, насколько тот или иной event loop загружен, чтобы понять, можно ли туда добавлять объекты.
Под рабочим потоком подразумевается задача, которая получает входные данные, занимается их обработкой никого не трогая и при завершении предоставляет результат. Для подобных задач eventloop не нужен.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 10:50
Под рабочим потоком подразумевается задача, которая получает входные данные, занимается их обработкой никого не трогая и при завершении предоставляет результат. Для подобных задач eventloop не нужен.
Для рабочих потоков без event loop нечего и огород городить - очередь заданий и вперед. Тут уже подсказывали.
Меня же интересует рабочий поток (ну, назовем его обслуживающий тогда, чтоб не было путаницы) с event loop'ом, который обслуживает QObject-ы. Ведь объектов может быть много и наверняка эффективнее обрабатывать их сигналы-слоты и события многопоточно. В целом ведь Qt это позволяет, но вот с нагрузкой на event loop - засада.


Название: Re: Нагрузка на поток
Отправлено: Igors от Февраль 25, 2010, 11:17
Это, в принципе, уже было предложено Igors. см. мой ответ http://www.prog.org.ru/index.php?topic=12552.msg79842#msg79842
Но это так, чисто академически. А вообще, основная засада тут в том, что рабочие потоки - асинхронны, т.е. принимают не задачи в виде данных, которые можно обработать и забыть, а в виде QObject-ов, которые крутятся в их цикле событий. Причем нагрузка от этих объектов принципиально непредсказуема - у одного может быть единственный обработчик сообщений, а у другого - полста сигналов и слотов на QueuedConnection. Поэтому поток не может принимать решение о том, что пора взять еще один объект на обработку, основываясь на количестве обслуживаемых объектов. Вот я и пытаюсь выделить критерий, чтобы понять, насколько загружен Event Loop потока.
Давайте разберемся. Выходит у Вас задачи привязаны к ниткам? Напр. клиент начинает что-то посылать. Ладно, послал первый пакет, она из ниток сервера его приняла. Теперь серия след. пакетов (от этого клиента) будет приходить на ту же нитку сервера. Но поскольку неизвестно когда клиент продолжит посылку - Вы занимаете нитку чем-то еще. Я верно понял?


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 11:26
Цитировать
итак, идея следующая: запросы (в вашей терминологии "задачи"?) складываются в простую очередь (QQueue) в порядке их получения (полагаю, это довольно честно, если учесть, что обычно клиенты не стараются заддосить сервер, а это - отдельная тема), а каждый рабочий поток сигнализирует о завершении обработки вверенного ему запроса - кто раньше отметился, что свободен, тот и получает на обработку следующий запрос из очереди (первый, если учесть, что у нас схема FIFO), если таковой имеется. при получении запроса от клиента создаём рабочие потоки по принципу стандартного пула (нужен новый и общее кол-во меньше максимального? - создаём; иначе выходим и первый освободившийся поток в дальнейшем получит данный запрос на обработку).
мысли/комментарии?
Это, в принципе, уже было предложено Igors. см. мой ответ http://www.prog.org.ru/index.php?topic=12552.msg79842#msg79842
вовсе нет. на пост Igors'а я и сам отвечал выше. принципиальная разница в том, что нет необходимости синхронизировать потоки при обращении к очереди задач (другими словами, не нужно защищать очередь задач мутексом), что снимает вопрос о негативном влиянии на производительность. при непустой очереди задач поток будет простаивать (между окончанием выполнения задачи и началом выполнения новой) лишь константное время, необходимое на выборку из QQueue. как характеризуется задача - набором байт или проинициализированным наследником QObject'а - в данном случае значения не играет абсолютно.
предыдущий комментарий был бы справедлив лишь в случае, когда задачи в очереди (объекты) взаимодействуют друг с другом между потоками (1) или в том же потоке (2), но  это накладывало бы непреодолимые ограничения на минимальное кол-во потоков (1) или на максимальную нагрузку на поток (2) и по большому счёту тогда и "взвешивать" затраты на работу с объектами не имеет смысла. методом исключения отбрасываю этот случай.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 11:45
предыдущий комментарий был бы справедлив лишь в случае, когда задачи в очереди (объекты) взаимодействуют друг с другом между потоками (1) или в том же потоке (2), но  это накладывало бы непреодолимые ограничения на минимальное кол-во потоков (1) или на максимальную нагрузку на поток (2) и по большому счёту тогда и "взвешивать" затраты на работу с объектами не имеет смысла. методом исключения отбрасываю этот случай.
Не совсем понял. Естественно, объекты, принятые потоками на обслуживание взаимодействуют друг с другом, в том числе и с объектами из соседних потоков (как правило, через отправку событий, но бывают и QueuedConnections). Иначе б зачем нужно было использовать здесь QObject и вообще о какой асинхронности можно говорить? Если говорить об объектах в очереди, ну, допустим, пока объект стоит в очереди, он не взаимодействует с остальными (неважно, как это может быть реализовано), но будучи принятым потоком на обслуживание встает в even-loop этого потока, который обрабатывает его события. Как я уже говорил, непонимание между нами именно в том, что поток не обрабатывает объекты и выкидывает их потом, а они в нем живут. А поток занимается обработкой их событий. Которых может и не быть. А может быть очень много. И поток, получается, неспособен оценить свою загрузку штатными методами, и принять решение о взятии нового объекта на обслуживание. И приходится изобретать костыли вроде таймеров для измерения латентности цикла событий в потоке.


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 11:53
в таком случае, я недопонимаю саму задачу...
скажем, пришёл запрос на получение содержимого такого-то файла - создали QFile (или чем там файл будет читаться?) и делегировали потоку; следующий запрос - скажем, удаление того же файла - создали объект и делегировали потоку. о чём эти объекты между собой общаются и почему они не будут удалены после отправки соответствующих ответов клиенту(ам)?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 12:09
Знаете, что? Бог с ними, с задачами, серверами и прочая-прочая. Давайте остановимся на многопоточности и асинхронности исполнения.
И переформулируем вопрос следующим образом:

1. Есть некоторое количество объектов-наследников QObject. Эти объекты могут быть инстансами разных классов, но у всех у них есть сигналы, слоты и обработчики событий.
2. Обработчики событий и слоты могут требовать некоторых вычислительных ресурсов.
3. Объектов может быть очень много, поэтому обработку их событий и соединений сигнал/слот нужно сделать многопоточной.
4. Обработчики событий и слоты могут опосредованно постить события объектам, находящимся как в собственном потоке, так и в других потоках.
5. Потоки не занимаются ничем, кроме обслуживания объектов, т.е. могут быть фактически инстансами QThread.
5. Во время выполнения объекты создаются и уничтожаются динамически, исходя из каких-либо внешних событий (ну, хоть взаимодействия с пользователем)
6. При создании нового объекта необходимо назначить ему обслуживающий поток (основной поток у нас занимается только отслеживанием внешних событий и созданием новых объектов)

Вопрос: каким образом при назначении обслуживающего потока выяснить его загрузку? Под загрузкой понимаем в данном случае латентность его цикла обработки событий, и на основании сравнения латентности с некоторым эмпирически полученным значением делаем вывод, стоит ли добавлять данный объект в исследуемый поток, или его event-loop и так предельно загружен и события объекта будут дольше стоять в очереди, чем исполняться, т.е. необходимо создать новый поток?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 12:18
в таком случае, я недопонимаю саму задачу...
скажем, пришёл запрос на получение содержимого такого-то файла - создали QFile (или чем там файл будет читаться?) и делегировали потоку; следующий запрос - скажем, удаление того же файла - создали объект и делегировали потоку. о чём эти объекты между собой общаются и почему они не будут удалены после отправки соответствующих ответов клиенту(ам)?
В целом - так оно и есть. Просто получение данных от клиентов и их некоторую часть их обслуживания, которая не делегируется бэкэнду, я тоже хочу сделать многопоточными. Т.е. в данном случае объекты - это наследники QObject-а, содержащие сокеты и еще кой-какую функциональность, и представляют собой абстракцию типа "клиентское подключение". Так вот обслуживание этих клиентских подключений я хочу сделать однородно-многопоточным, и для того-то мне и нужно определять загруженность цикла событий потока.


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 13:29
мне кажется, это перебор...
в любом случае, всё ещё не понимаю о чём эти объекты-клиентские подключения между собой общаются


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 13:32
Все таки я не вижу смысла в таком распределении ролей.  ;)
Есть разные задачи, для каждого вида задач есть специальный класс, все они являются наследниками класса Job.
Как они устроенны внутри пока забываем - черный ящик. Объект такого класса получает входные параметры, выполняет саму работу и при завершении сообщает нам, что такая-то задача завершилось и можно забрать результат.
Пользователю нужно посчитать контрольную сумму файла, он создал объект класса CalcFileChecksumJob, в конструкторе передав имя файла и положил в пул задач. Пул поручает эту работу первой простаивающей нитке.
В дальнейшем, появился новый тип задач, дописали соответствующий класс. Никаких дополнительных телодвижений для его интеграции в систему делать не нужно.

Теперь, по поводу реализации задач. Некоторым задачам вообще не нужен цикл обработки событий (открыл файл, пробежался по нему, закрыл файл, завершился), для чего его запускать. Для других нужен - создали необходимые объекты для выполнения (QTcpSocket, QTimer, ...), запустили цикл, задача выполнилась - все остановили, завершились.

Т.е. реализация каждого задания спрятана в своем классе, нужно что-то поменять/изменить - пожалуйста, основная система ничего и знать не будет.




Название: Re: Нагрузка на поток
Отправлено: Igors от Февраль 25, 2010, 13:52
и положил в пул задач. Пул поручает эту работу первой простаивающей нитке.
Ну может не первой а той что больше всего простояла. Но что пул нужен - это точно. Нехорошо помещать задачу в очередь нитки если она занята. На эмпирические оценки надежда слабая - хотя бы потому что нагрузка может измениться на ходу.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 13:58
Ну может не первой а той что больше всего простояла.
А в чем разница, между свободной ниткой которая долго простояла и той, которая только что освободилась?
Первая лучше отдохнула?  :)


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 14:03
мне кажется, это перебор...
в любом случае, всё ещё не понимаю о чём эти объекты-клиентские подключения между собой общаются
В принципе, в контексте вопроса это не так важно. Они могут и не общаться между собой вовсе, но все равно обязаны обрабатывать события, полученные от остальных участников системы. Которыми, в частности, могут быть и другие клиентские подключения. Ну, например, скажем, пришли объекту-клиенту данные, он их десериализовал, вызвал локальный обработчик, который выяснил, что эти данные нафик никому не нужны, и предназначаются к немедленной отправке другому клиенту. Обработчик дернет из списка ссылку на объект другого клиентского подключения, и, если тот существует (бишь, клиент подключен), то, непосредственно, минуя бэкэнд, поставит этому клиенту эти данные в очередь на отправку (запостив ему соответствующее событие с ссылкой на эти данные). Другое дело, если клиента в онлайне нет, и тогда обработчик встанет в очередь на обращение к бэкэнду (там шедулер предусмотрен, ибо бэкэндов может быть несколько), тот поставит эти данные в очередь deferred data, и займется другими делами. Диспетчер подключений при подключении нового клиента опрашивает бэкэнд на предмет наличия для клиента данных очереди deferred data, и если таковые имеются, отдаст их на отправку. Можно не морочится, и сделать все по второй схеме, но это во-первых дополнительно загрузит бэкэнд, а во-вторых, по прежнему остается та же проблема с обработкой сообщений от бэкэнда. Ведь бэкэнд живет в другом потоке, и общаться с клиентским подключением (читай, вызывать его методы) напрямую не имеет права, только через события.

Возможно, это действительно перебор, но и вне контекста клиент-сервер задача (вот эта: http://www.prog.org.ru/index.php?topic=12552.msg79885#msg79885 ) определенно имеет право на жизнь, и штатной возможности определения латентности цикла событий явно не хватает. Понятно, для GUI это не нужно, там всего один поток, но ведь Qt это уже давно больше чем GUI. :)


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 14:10
Все таки я не вижу смысла в таком распределении ролей.  ;)
Есть разные задачи, для каждого вида задач есть специальный класс, все они являются наследниками класса Job.
Как они устроенны внутри пока забываем - черный ящик. Объект такого класса получает входные параметры, выполняет саму работу и при завершении сообщает нам, что такая-то задача завершилось и можно забрать результат.
Пользователю нужно посчитать контрольную сумму файла, он создал объект класса CalcFileChecksumJob, в конструкторе передав имя файла и положил в пул задач. Пул поручает эту работу первой простаивающей нитке.
В дальнейшем, появился новый тип задач, дописали соответствующий класс. Никаких дополнительных телодвижений для его интеграции в систему делать не нужно.
Теперь, по поводу реализации задач. Некоторым задачам вообще не нужен цикл обработки событий (открыл файл, пробежался по нему, закрыл файл, завершился), для чего его запускать. Для других нужен - создали необходимые объекты для выполнения (QTcpSocket, QTimer, ...), запустили цикл, задача выполнилась - все остановили, завершились.
Т.е. реализация каждого задания спрятана в своем классе, нужно что-то поменять/изменить - пожалуйста, основная система ничего и знать не будет.
Повторюсь - речь идет не о задачах и распределении их по потокам, а об объектах, эти задачи генерирующих. Эти объекты управляются внешними событиями, например, данными из сокета, и формируют из этих данных задачи и отправляют бэкэнду в очередь. Эти объекты живут в нескольких потоках, и обязаны обрабатывать сообщения от бэк-энда и друг от друга. Просто изначально я избрал немного неверную терминологию, и теперь мучаюсь, пытаясь объяснить, чего же я все-таки хочу.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 14:32
Давай будем смотреть на объекты.  :)
Есть объект-поток ConnectionManager, в нем находятся все объекты-соединения с каждым клиентов Connection. В этом потоке запущен eventloop.
У одного из соединений срабатывает сигнал readyRead, в контексте потока ConnectionManager обрабатывается слот, который читает данные из нужного сокета и определяет, что команда пришла полностью. В этот момент испускает сигнал newCommand( Command ).  Все ConnecionManager отправл команду и забыл...
В объекте класса Command содержится вся информация о команде. Кто, где и в каком потоке будет ловить этот сигнал решать тебе.
Предположим он ловиться в объекта-потоке CommandManager. Он в свою очередь решает какой объект нужно создать для решения поступившей команды, создает его и кладет в пул задач. Все, CommandManager забыл о задачи, до тех пор пока она не будет завершена.
Как только результат сформирован, CommandManager посылает сигнал readyResult, его обрабатывает ConnectionManager и ставит в очередь на отправку данные для клиента.

Т.е. все разделено, CommandManager не может на пряму отсылать данные через сокет, он может попросить об этом ConnectionManager с помошью сигналов.
Если сразу продумать интерфейсы и протоколы такого взаимодействия, то разные части системы не будут зависить друг от друга.

Или мы опять про разные вещи говорим?  :)


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 14:58
Давай будем смотреть на объекты.  :)
Есть объект-поток ConnectionManager, в нем находятся все объекты-соединения с каждым клиентов Connection. В этом потоке запущен eventloop.
[skipped]
Т.е. все разделено, CommandManager не может на пряму отсылать данные через сокет, он может попросить об этом ConnectionManager с помошью сигналов.
Если сразу продумать интерфейсы и протоколы такого взаимодействия, то разные части системы не будут зависить друг от друга.
Или мы опять про разные вещи говорим?  :)
Уже горячо. :) Единственная разница - я хочу сам ConnectionManager сделать многопоточным, т.е. он сам не поток, а имеет в своем распоряжении несколько потоков, в которых живут объекты-соединения CClientConnection. Вообще, сам класс ConnectionManager (у меня называется CConnectionDispatcher) хранит QMap<CUserId, CClientConnection*>, где CUserId = login+peeraddress, обновляет его при коннектах-дисконнектах и предоставляет кому угодно ссылки на объекты-подключения. При этом у объектов-подключений нет ни единого публичного метода, кроме CUserId getUserId(), т.е. инкапсуляция не нарушается, а все коммуникации осуществляются через события. При этом CClientConnection сам принимает данные от своих сокетов, десериализует их, вызывает для них обработчик, который уже решает, что с ними делать - то ли проэмитить на всю систему сигнал incomingData(data*), а там глядишь кто-нибудь поймает, то ли разобраться с этими данными самостоятельно (например, если эти данные - текстовое сообщение другому клиенту, или, того хуже клиент спрашивает, сколько у него открытых тасков на сервере, а это уже локальные данные и для самого объекта CClientConnection). Т.е. весь вопрос в том, как держать объекты-соединения в нескольких потоках.


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 15:06
передача данных от одного клиента другому - плохой пример. клиент ведь не просил эти данные) если уж на то пошло, пущай соединяются между собой и травят друг-друга хоть терабайтами мусора - никакой нагрузки на сервер.
а вот другое дело, что в описанном примере каждый клиент-задача-объект (я уж запутался где кто) знает о существовании других клиентов-задач-объектов и даже может их найти и чего-то им сказать:)
закрадывается подозрение, что архитектура плохо продумана...


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 15:16
Цитировать
Вообще, сам класс ConnectionManager (у меня называется CConnectionDispatcher) хранит QMap<CUserId, CClientConnection*>, где CUserId = login+peeraddress, обновляет его при коннектах-дисконнектах и предоставляет кому угодно ссылки на объекты-подключения.
т.о. каждый CClientConnection имеет указатель на CConnectionDispatcher и может попросить у него указатель или список указателей на другие CClientConnection'ы, у которых нет публичных членов? и полученный указатель на CClientConnection в другом CClientConnection будет использоваться для того, чтобы отправить этому объекту событие? правильно?


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 16:05
Вообще, сам класс ConnectionManager (у меня называется CConnectionDispatcher) хранит QMap<CUserId, CClientConnection*>, где CUserId = login+peeraddress, обновляет его при коннектах-дисконнектах и предоставляет кому угодно ссылки на объекты-подключения.
Для чего? Чем меньше подсистемы будут знать друг о друге, тем лучше. Каждая подсистема должна выполнять свою роль и ничего больше. Для взаимодействия с ней должны быть предусмотренны "ручки" (интерфейс).

В твоем случае я бы делал примерно так:
Есть объект CConnectionDispatcher, который живет в главном потоке. Внутри у него работает QTcpServer, который отлавливает новые подключения и вызывает нужные методы для создания объектов CClientConnection.
Объект CConnectionDispatcher владеет несколькими объектами-потоками для обработки соединений ConnectionPool.
С внешним миром объекты ConnectionPool общаются используя несколько ручек:
* публичный слот addClientConnection( CUserId id, int socketDescriptor ). Создает и добавляет объект CClientConnection в свою коллекцию, настраивает коннекты, ... Все это происходит в контексте соответствующего потока.
* сигнал newCommand( CUserId id, Command cmd ). Этот сигнал отсылается при поступлении новой команды от одного из клиентов.
* публичный слот sendData( CUserId id, Data data ). Этот слот добавляет данные для отдачи нужному клиенту.

Класс и объекты ConnectionPool спрятаны внутри реализации CConnectionDispatcher, другие подсистемы про него вообще ничего не знают, они с соединениями работают только через ручки предоставленные CConnectionDispatcher. Что это за ручки:
* сигнал newCommand( CUserId id, Command cmd ).
* публичный слот sendData( CUserId id, Data data ).


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 17:43
т.о. каждый CClientConnection имеет указатель на CConnectionDispatcher и может попросить у него указатель или список указателей на другие CClientConnection'ы, у которых нет публичных членов? и полученный указатель на CClientConnection в другом CClientConnection будет использоваться для того, чтобы отправить этому объекту событие? правильно?
Не совсем. CClientConnection ничего не знает ни про другие объекты-подключения, ни про CConnectionDispatcher. Общаются они опосредованно, через иерархию классов-обработчиков данных. Просто я не указывал это звено для краткости изложения. Тем более, что к моему вопросу это никакого отношения не имеет. Эти обработчики - это легкие псевдо-функторы (я функторы не люблю, они запутывают), которые вызываются объектом-подключением после десериализации данных. Они принимают на вход данные (каждому типу данных поставлен в соответствие обработчик) и контекст, в котором эти данные были получены (в данном случае - пришли из сокета), и принимают решение, что с этими данными делать - то ли отправить бэкэнду на обработку, то ли отправить их напрямую другому объекту-подключению, а может, еще чего-нибудь. Т.е. единственная причина для объекта-подключения, по которой он может захотеть связаться с другим таким же объектом - это то, что ему пришли данные, которые этого требуют. Но это решение, как и собственно отправку события, осуществляет не сам объект, а обработчик данных. Не знаю, может быть терминология не совсем верна - по-анлийски он назвается data handler.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 17:57
Для чего? Чем меньше подсистемы будут знать друг о друге, тем лучше. Каждая подсистема должна выполнять свою роль и ничего больше. Для взаимодействия с ней должны быть
[skip]
С внешним миром объекты ConnectionPool общаются используя несколько ручек:
* публичный слот addClientConnection( CUserId id, int socketDescriptor ). Создает и добавляет объект CClientConnection в свою коллекцию, настраивает коннекты, ... Все это происходит в контексте соответствующего потока.
[skip]
* публичный слот sendData( CUserId id, Data data ).
Друзья! Я прошу обратить внимание, что я не просил ни анализировать архитектуру моего проекта, ни предлагать советы по ее проектированию, ни убеждать меня, почему то, о чем я спрашиваю, мне не нужно. Я всего лишь просил ответа или советов и размышлений по конкретному техническому вопросу, а именно, как выяснить нагрузку на even loop потока.
Конечно, ваши советы весьма ценны и наталкивают меня на очень важные размышления, но тем не менее, они отвлекают от основной проблемы треда - той, что обозначена в первом посте, и обобщена здесь: http://www.prog.org.ru/index.php?topic=12552.msg79885#msg79885. На весь тред из пятидесяти сообщений всего два или три комментария по существу.

BRE,  с точностью до ограничения общности у меня все реализовано именно так, как ты описываешь. Есть расхождения в деталях, но они несущественны. Внимание, вопрос! Именно по той модели, что ты предлагаешь:
Коль скоро у объекта CConnectionDispatcher есть несколько объектов-потоков ConnectionPool, на каком основании он должен принимать решение, какому из них отдать пришедшее подключение, вызвав метод addConnection()? Или может не нужно этого делать вовсе, а стоит создать новый объект CConnectionPool и отдать подключение ему?


Название: Re: Нагрузка на поток
Отправлено: ритт от Февраль 25, 2010, 18:10
ну, а чем вариант в http://www.prog.org.ru/index.php?topic=12552.msg79866#msg79866 не устраивает?


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 18:18
Коль скоро у объекта CConnectionDispatcher есть несколько объектов-потоков ConnectionPool, на каком основании он должен принимать решение, какому из них отдать пришедшее подключение, вызвав метод addConnection()? Или может не нужно этого делать вовсе, а стоит создать новый объект CConnectionPool и отдать подключение ему?
Т.к. основную нагрузку на эти потоки будет создавать отдача данных, вижу несколько вариантов:
* Если отдача идет равными кусками по очередь всем клиентам, то относительную нагрузку можно считать по количеству соединений, которые производят отдачу данных.
Например, в первом пуле 100 соединений, 20 из которых отдают данные клиенту, а во втором 30 соединений, 25 из которых отдают данные. Можно считать что второй пул загружен сильнее.
* Если размер куска для каждого клиента может быть разным, то можно суммировать размеры кусков в каждом пуле и решать из общего объема, который должен отдать поток за итерацию.
* И третий вариант, замерять время итерации eventloop и сравнивать их.

За навязчивые советы прости...  ;)


Название: Re: Нагрузка на поток
Отправлено: BigZ от Февраль 25, 2010, 18:33
Цитата: kramer3d
Вопрос в следующем: есть ли способ оценить нагрузку на поток (т.е. эффективность его Event-Loop'a) средствами Qt, и, если эта нагрузка превысит критический уровень, создать новый?
Любым профайлером можно померить. Например AQTime неплохо справляется с такими задачами если под Windows. После анализа заводишь константу - колличество соединений на поток. Констант может быть несколько в зависимости от типа железа на котором запускается сервер.
Цитата: kramer3d
Внимание, вопрос! Именно по той модели, что ты предлагаешь:
Коль скоро у объекта CConnectionDispatcher есть несколько объектов-потоков ConnectionPool, на каком основании он должен принимать решение, какому из них отдать пришедшее подключение, вызвав метод addConnection()?
У потока заводишь атомарный флаг- Busy. Перед началом обработки твоих данных выставляешь в true. После выхода из обработки сбрасываешь в false. В CConnectionDispatcher управление передаёшь первому попавшемуся, который не Busy.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 18:46
ну, а чем вариант в http://www.prog.org.ru/index.php?topic=12552.msg79866#msg79866 не устраивает?
Да в принципе всем устраивает. Просто изначально мне казалось, что где-то глубоко в Qt есть секретная функция измерения латентности эвент-пула, о которой я не знаю. После тщательного чтения доки и не менее тщательного ковыряния в исходниках стало понятно, что это не так. Теперь я просто прошу совета гуру по поводу конкретных реализаций этой функциональности, т.е. какие тут могут быть подводные камни, и на какие уже, возможно, известные грабли мне не следует наступать.
Т.к. основную нагрузку на эти потоки будет создавать отдача данных, вижу несколько вариантов:
* Если отдача идет равными кусками по очередь всем клиентам, то относительную нагрузку можно считать по количеству соединений, которые производят отдачу данных.
Например, в первом пуле 100 соединений, 20 из которых отдают данные клиенту, а во втором 30 соединений, 25 из которых отдают данные. Можно считать что второй пул загружен сильнее.
* Если размер куска для каждого клиента может быть разным, то можно суммировать размеры кусков в каждом пуле и решать из общего объема, который должен отдать поток за итерацию.
* И третий вариант, замерять время итерации eventloop и сравнивать их.
За навязчивые советы прости...  ;)
Да нет, советы очень кстати. Они заставляют меня переосмыслить мою собственную архитектуру, и, пока есть такая возможность, вносить в нее изменения. Просто они отвлекают нас от сути вопроса, и отвлекают капитально. По существу: первые два варианта, в сущности, эквивалентны, и сводятся к учету суммарного трафика для клиента. Все бы было хорошо, но есть одна проблема - клиенты вызывают обработчики данных, не зная, что за код в них выполняется. И сервер понятия не имеет об этом. И типы данных, и их обработчики во фронтэнде поставляются отдельно в виде динамических библиотек. Сервер их на ходу линкует, регистрирует типы данных и обработчики, которые используют API бэкэнда. Конечно, по тех. требованиям API обработчики должны быть крохотными, и принимать решения на лету, не стопоря поток, но мне не хочется накладывать подобные ограничения на их функциональность только исходя из требований обеспечения многопоточности. И потом, с точки зрения безопасности времени исполнения, если обработчик будет содержать runtime ошибку, стопорящую поток, что же, это поставит всю систему в коленно-локтевую, снизив удельную производительность потоков?
Ну, а по поводу третьего варианта см.выше. :)


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 18:56
Я с самого начала треда предлагаю на поток(и) фронтэнда возложить единственную функцию отправку/получение данных, всю остальную обработку проводить в других потоках и эти варианты оценки как раз для этого метода.
Ты же хочешь в этих потоках еще и работу работать. А это может остановить обслуживание всех клиентов из этим потоком. Кстати об этом уже писалось.  :)
Почему ты хочешь в этих потоках работу работать?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 19:08
Любым профайлером можно померить. Например AQTime неплохо справляется с такими задачами если под Windows.
Что ж, профайлер за собой таскать? И запускать его во времени исполнения? ;) Кроме шуток, профайлер выдаст мне нагрузку потока на CPU, которая может быть стопроцентной (т.е. поток не простаивает), но при этом это не обязательно означает, что поток в моих терминах стопроцентно загружен, т.е. в него бессмысленно добавлять новые объекты для обслуживания.
У потока заводишь атомарный флаг- Busy. Перед началом обработки твоих данных выставляешь в true. После выхода из обработки сбрасываешь в false. В CConnectionDispatcher управление передаёшь первому попавшемуся, который не Busy.
Пожалуйста, перечитайте тред. Например, http://www.prog.org.ru/index.php?topic=12552.msg79885#msg79885, http://www.prog.org.ru/index.php?topic=12552.msg79880#msg79880, http://www.prog.org.ru/index.php?topic=12552.msg79916#msg79916. Суть в том, что это не просто потоки, которые обрабатывают пришедшие каким-либо образом данные и потом забывают про них, а потоки, которые обслуживают объекты-наследники QObject, т.е. предоставляют им свой Event Loop для обработки их событий.

P.S. На QtForum.org сказали, что в Qt такой функциональности нет, точней, сказали, что есть QThreadPool и QThreadStorage, но они мне врядли помогут. И посоветовали посмотреть на библиотечку http://www.threadingbuildingblocks.org (http://www.threadingbuildingblocks.org) (UPD: матушка моя, как же тут ссылки-то вставлять?). Я, конечно, посмотрю, но врядли она мне сможет предложить что-то большее, чем предложит профайлер (см. выше почему это не совсем подходит). Тем более что привлекать стороннюю библиотеку, да еще коммерческую, да еще и от Intel, это в данном случае не совсем разумно. Благо она опенсорсная, так что я подсмотрю, какие решения там используются, и если найду что-нибудь подобающее, отчитаюсь здесь.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 19:18
Я с самого начала треда предлагаю на поток(и) фронтэнда возложить единственную функцию отправку/получение данных, всю остальную обработку проводить в других потоках и эти варианты оценки как раз для этого метода.
Ты же хочешь в этих потоках еще и работу работать. А это может остановить обслуживание всех клиентов из этим потоком. Кстати об этом уже писалось.  :)
Почему ты хочешь в этих потоках работу работать?
Тех. требования. Масштабируемость и расширяемость. В частности, возможность внедрять новые типы данных без остановки сервера. Кроме того, все-таки я в потоке фронтэнда в вызове обработчика работу не работаю, нет. Я там определяю, есть ли для этих данных работа и как ее работать, после чего передаю их на бэкэнд. А то может ее вовсе нет, и все что надо, это эти данные передать другому клиенту. Но я не хочу, чтобы если какой-нибудь обработчик-гад (я ведь их не контролирую, точней, контролирую, но не все) решит все-таки в треде фронтэнда работу поработать, это бы свалило сервер.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 19:37
Подожди.
Есть несколько потоков фронтэнда, которые либо принимают данные, либо отсылают их. Тогда как нагрузка на них зависит от тех заданий, которые выполняются в бэкэнде???
По-моему никак, их загрузка зависит только от трафика и ее можно прикинуть.

Цитировать
Я там определяю, есть ли для этих данных работа и как ее работать, после чего передаю их на бэкэнд. А то может ее вовсе нет, и все что надо, это эти данные передать другому клиенту. Но я не хочу, чтобы если какой-нибудь обработчик-гад (я ведь их не контролирую, точней, контролирую, но не все) решит все-таки в треде фронтэнда работу поработать, это бы свалило сервер.
А почему не передать сразу из фронтэнда в другую нить (бэкенду?) полученные данных и пусть он, не тормозя фронтэнд, будет принимать решение о этом пакете?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 25, 2010, 19:50
А почему не передать сразу из фронтэнда в другую нить (бэкенду?) полученные данных и пусть он, не тормозя фронтэнд, будет принимать решение о этом пакете?
Если класс, живущий в одной нитке, вызывает метод класса, живущего в другой нитке (или не живущего нигде, если он не QObject, не важно) выполнение этого метода будет происходить в треде функции, вызвавшей метод (забудем пока о том, что это небезопасно). Т.е. для передачи данных в другую нитку для принятия решений об их обработке мне нужно обработчики сделать наследниками QObject и держать для них отдельную очередь событий. Для нее встает та же проблема масштабируемости: если поток обработчиков перегружен событиями, на основании чего я должен это узнать и создать новый?


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 25, 2010, 20:14
Т.е. для передачи данных в другую нитку для принятия решений об их обработке мне нужно обработчики сделать наследниками QObject и держать для них отдельную очередь событий. Для нее встает та же проблема масштабируемости: если поток обработчиков перегружен событиями, на основании чего я должен это узнать и создать новый?
Если использовать сигналы для межпотокового общения, то в качестве очереди будет использоваться очередь событий потока получателя.

IMHO, пропускная способность потока принятия решения, будет перекрывать все потребности фронтэнда. Ведь ему нужно будет только принять решение и запустить или не запустить другой поток для выполнения работы.
Конечно, все эти мысли без конкретных спецификаций на протокол могут быть ошибочными.


Название: Re: Нагрузка на поток
Отправлено: BigZ от Февраль 25, 2010, 21:31
Что ж, профайлер за собой таскать? И запускать его во времени исполнения? ;) Кроме шуток, профайлер выдаст мне нагрузку потока на CPU, которая может быть стопроцентной (т.е. поток не простаивает), но при этом это не обязательно означает, что поток в моих терминах стопроцентно загружен, т.е. в него бессмысленно добавлять новые объекты для обслуживания.
Если поток в настоящий момент загружен на 100%, какое условие должно выполнится, чтобы ты считал, что в пул ещё можно добавить соединение? Ты написал, что у тебя нет информации, сколько будет выполнятся внешняя функция обработчик (из dll например или SSL шифрование). То есть получается, что задача не решаема. Даже если ты будешь знать
что в пуле только одно соединение, то всё равно не сможешь точно сказать, когда оно будет обработано и принять решение добавлять или нет.
Я собственно предлагал, экспериментальным путём при помощи профайлера получить среднее время выполнения и потом эти данные использовать.



Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 26, 2010, 08:08
Если поток в настоящий момент загружен на 100%, какое условие должно выполнится, чтобы ты считал, что в пул ещё можно добавить соединение?
Если поток в настоящий момент загружен на 100%, это означает, что он выполняет какой-то код, а не крутит event-loop в холостую. Причем это довольно слабо характеризует нагрузку на event-loop потока, если только не производить подобные измерения регулярно, и не усреднять полученные значения.
Ты написал, что у тебя нет информации, сколько будет выполнятся внешняя функция обработчик (из dll например или SSL шифрование). То есть получается, что задача не решаема. Даже если ты будешь знать что в пуле только одно соединение, то всё равно не сможешь точно сказать, когда оно будет обработано и принять решение добавлять или нет. Я собственно предлагал, экспериментальным путём при помощи профайлера получить среднее время выполнения и потом эти данные использовать.
Обработано - это не значит "прожевано и забыто". Вообще, "обработано" в данном случае не совсем корректный термин. Более корректно употреблять термин "обслуживается", так как это соединение - это объект-QObject, который может получать события извне и должен на них реагировать. Экспериментальным путем при помощи профайлера можно, конечно, замерить ресурсоемкость всех методов, обрабатывающий события QObject-а, а так же всех методов, обрабатывающих события его членов, а потом эту относительную ресурсоемкость суммировать и вычислять такми образом суммарную нагрузку на поток. Но это даст нам скорее статическую картину, да и далеко не факт, что стопроцентно достоверную. И потом, что же, если я захочу изменить что-то в коде или добавить новой функциональности, мне придется все эти измерения заново проводить? И как быть с кроссплатформенностью? Ведь на разных ОС потоки выполняются и управляются по разному. И у Qt на разных ОС разная низкоуровневая начинка для управления потоками да и вообще, для эффективного обслуживания QObject.


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 26, 2010, 09:37
Если использовать сигналы для межпотокового общения, то в качестве очереди будет использоваться очередь событий потока получателя.
О том и речь. Сигналы QueuedConnection и события - это в сущности одно и тоже.
IMHO, пропускная способность потока принятия решения, будет перекрывать все потребности фронтэнда. Ведь ему нужно будет только принять решение и запустить или не запустить другой поток для выполнения работы.
Конечно, все эти мысли без конкретных спецификаций на протокол могут быть ошибочными.
В этом случае опять накладываются ограничения на масштабируемость. Т.е. я могу расширять фронтенд, занимающийся получением/отправкой данных, но не могу расширять модуль обработчиков, который не является многопоточным. Конечно, его пропускная способность с головой перекрывает потребности фронтенда, но только до поры, до времени. Т.е. когда в силу увеличившейся нагрузки на систему (аппаратные ресурсы у нас не ограничены, будем считать) я создам 50 (или 500, не важно) потоков во фронтенде, единственный поток обработчиков данных станет "бутылочным горлышком" и будет тормозить рост производительности всей системы.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 26, 2010, 10:11
При обсуждении абстрактных вещей очень сложно говорить о конкретных задержках. Давай будет конкретизировать.
Есть класс Job, который описывает "задачу". Пока решаем, что этот класс наследуется от QThread. Все виды задач, которые может выполнять сервер, описывается своим классом наследником Job. Эти классы могут быть определены как в основной программе, так и во внешних разделяемых библиотеках. При старте системы все эти классы регистрируются в фабрике задач JobFactory.
За создание объекта задачи отвечает ее метод:
Job *JobFactory::build( CUserId id, const Command &cmd );
Если для указанной команды нет подходящей задачи, метод вернет 0. Время создания новой задачи константное.

Переходим в слот получения данных readyRead:
* читаем данные из сокета
* проверяем, что прошел весь пакет, если нет - выходим из слота
* формируем объект cmd из полученных данных
* создаем объект Job с помощью фабрики, если объект не создался - выходим из слота
* запускаем новый поток описываемый этим объектом Job

Дальше Job для своей работы может создать еще 100 потоков, работать не эффективно, зависнуть, но от него не будет зависеть работа остальной системы.


Название: Re: Нагрузка на поток
Отправлено: BigZ от Февраль 26, 2010, 10:24
Если поток в настоящий момент загружен на 100%, это означает, что он выполняет какой-то код, а не крутит event-loop в холостую. Причем это довольно слабо характеризует нагрузку на event-loop потока, если только не производить подобные измерения регулярно, и не усреднять полученные значения.

Обработано - это не значит "прожевано и забыто". Вообще, "обработано" в данном случае не совсем корректный термин. Более корректно употреблять термин "обслуживается",

Тогда не совсем понятно, какую информацию ты хочешь получить.

Потери в твоей схеме
состоят из двух частей – потери при маршалинге событий в event-loop (то есть потери в Qt) и потери в твоих обработчиках. Могу тебя уверить, что event-loop Qt работает быстро и потерями в нём можно пренебречь. Но даже если этого не достаточно, думаю, ты можешь получить количество событий в очереди и на основе их числа принимать решения. Однако по сути это тебе ни чего не даст, так как если поток загружен на 100% и в очереди всего одно сообщение, то ты всё равно не можешь быть уверен, что след сообщение будет быстро обработано, так как твой обработчик может выполнятся час.
Правильным кажется в этой ситуации создавать новый поток если в данный момент все потоки загружены на 100%.

То есть тебе вначале нужно всё-таки определится какую характеристику ты хочешь получить, ну вот какой метод в Qt тебя бы устроил?


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 26, 2010, 11:09
При обсуждении абстрактных вещей очень сложно говорить о конкретных задержках. Давай будет конкретизировать.
[skip]
* запускаем новый поток описываемый этим объектом Job
Дальше Job для своей работы может создать еще 100 потоков, работать не эффективно, зависнуть, но от него не будет зависеть работа остальной системы.
Не, немного не в ту степь. Получается, что каждый следующий пакет данных будет обрабатываться в своем потоке. Threading overhead будет сумасшедший.
Тогда не совсем понятно, какую информацию ты хочешь получить.
Потери в твоей схеме
состоят из двух частей – потери при маршалинге событий в event-loop (то есть потери в Qt) и потери в твоих обработчиках. Могу тебя уверить, что event-loop Qt работает быстро и потерями в нём можно пренебречь. Но даже если этого не достаточно, думаю, ты можешь получить количество событий в очереди и на основе их числа принимать решения. Однако по сути это тебе ни чего не даст, так как если поток загружен на 100% и в очереди всего одно сообщение, то ты всё равно не можешь быть уверен, что след сообщение будет быстро обработано, так как твой обработчик может выполнятся час.
Правильным кажется в этой ситуации создавать новый поток если в данный момент все потоки загружены на 100%.
То есть тебе вначале нужно всё-таки определится какую характеристику ты хочешь получить, ну вот какой метод в Qt тебя бы устроил?
Да нет в Qt такого метода. Надо его самому реализовать, вопрос в том, как. Дело в том, что мне нужна не моментальная локальная загруженность потока, а некоторая интегральная характеристика его загруженности. Т.е. локальная загруженность может оказаться 100%, так как поток в настоящее время выполняет какой-нибудь обработчик. Но через полсекунды выполнение завершится, и поток снова полчаса будет стоять без дела. А я уже создал новый поток и отдал новый объект ему на обслуживание.
Предполагается, что обслуживаемые объекты генерируют более-менее постоянную нагрузку на поток, то есть характер операций, выполняемых объектом, меняется медленно (относительно). Но для каждого объекта эта нагрузка может быть разной, и, вообще говоря, она не поддается прогнозированию. Мне нужно считать некую интегральную характеристику загруженности. Мммм.. Ну, хоть статистику за период времени, чтоли. Т.е. полчаса стоял без дела, полсекунды работал - значит смело можно добавлять ему новые объекты. Поэтому-то я и завел речь об усредненной латентности цикла событий. Она, мне кажется, вполне может быть такой характеристикой, а чтобы ее посчитать, не надо через каждые три миллисекунды мерять загрузку потока, достаточно по изменению статуса объектов (обработалось событие, или объект удалился из потока) запускать в event loop событие с таймстемпом, и в обработчике мерять/усреднять время, которое оно простояло в очереди.

Друзья! Я сейчас доклепаю прототип, иллюстрирующий эту задачу, погоняю его немного и запощу код сюда. Тогда мы продолжим обсуждение уже в конкретной проблематике. А то толчем воду в ступе с абстракциями непонятными.


Название: Re: Нагрузка на поток
Отправлено: BigZ от Февраль 26, 2010, 12:56
Дело в том, что мне нужна не моментальная локальная загруженность потока, а некоторая интегральная характеристика его загруженности. Т.е. локальная загруженность может оказаться 100%, так как поток в настоящее время выполняет какой-нибудь обработчик. Но через полсекунды выполнение завершится, и поток снова полчаса будет стоять без дела. А я уже создал новый поток и отдал новый объект ему на обслуживание.
Предполагается, что обслуживаемые объекты генерируют более-менее постоянную нагрузку на поток, то есть характер операций, выполняемых объектом, меняется медленно (относительно). Но для каждого объекта эта нагрузка может быть разной, и, вообще говоря, она не поддается прогнозированию. Мне нужно считать некую интегральную характеристику загруженности. Мммм.. Ну, хоть статистику за период времени, чтоли. Т.е. полчаса стоял без дела, полсекунды работал - значит смело можно добавлять ему новые объекты.

Ну тогда запускай вначале свой сервер в не оптимальном режиме и на первом этапе формируй базу статистики нагрузки, то есть, как бы обучаешь сервер. После скажем часа работы можно начинать перераспределять задания с учётом накопленных измерений.
То есть если ты такое сделаешь, то мы получим первый в мире потоковый сервер с искусственным интеллектом и способностью обучатся :)

P.S. Можно даже сказать с применением нано-технологий... :)


Название: Re: Нагрузка на поток
Отправлено: Igors от Февраль 26, 2010, 13:33
Ну тогда запускай вначале свой сервер в не оптимальном режиме и на первом этапе формируй базу статистики нагрузки, то есть, как бы обучаешь сервер. После скажем часа работы можно начинать перераспределять задания с учётом накопленных измерений.
То есть если ты такое сделаешь, то мы получим первый в мире потоковый сервер с искусственным интеллектом и способностью обучатся :)

P.S. Можно даже сказать с применением нано-технологий... :)
Ну может слишком громко сказано об интеллекте, но мысль хорошая, практичная. Засекать время каждого пакета (годится и для новых) и затем опираться на эту статистику. Понятно что она не идеальна (напр. ОС отдал все на back-end) но она ловит "пропорции". Реализация не сложна, без тестового режима можно обойтись, до тех пор пока возникнут проблемы с загрузкой - статистика накопится.


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 26, 2010, 13:35
Не, немного не в ту степь. Получается, что каждый следующий пакет данных будет обрабатываться в своем потоке.
Да, каждое новое задание будет обрабатываться в новом потоке.
Не обязательно запускать каждый раз новый поток, а потом его убивать. Можно воспользоваться пулом уже запущенных потоков (пример QThreadPool). Эту идею я уже тоже описывал.
При старте запускаются для примера 50 потоков, которые запираются на мьютексе и не забирают ресурсы системы. Появилось новое задание, взяли уже запущенный поток, выполнили в нем работу (очень быстро или очень медленно, не важно) и вернули обратно в пул. Никакого оверхеда на создание/завершение потока не будет.
Если в пуле потоки закончились, создали еще 50 новых. Добавлением/удалением потоков из пула. может заниматься отдельный поток.

В общем, пока я каких-то проблем с подобных алгоритмом не вижу...  ::)


Название: Re: Нагрузка на поток
Отправлено: kramer3d от Февраль 26, 2010, 15:26
Да, каждое новое задание будет обрабатываться в новом потоке.
Не обязательно запускать каждый раз новый поток, а потом его убивать. Можно воспользоваться пулом уже запущенных потоков (пример QThreadPool). Эту идею я
[skip]
В общем, пока я каких-то проблем с подобных алгоритмом не вижу...  ::)
Т.е. ты настаиваешь на отказе от эвент-лупа. Разумно, в принципе, от него в данном случае отдни проблемы.
Реализовать, например так:
Код:
class Functor
{
public:
  void operator()(Data* data)
  {
    if ( data )
      ... //process data;
  }
};

class Job : private QThread {
  Q_OBJECT
public:
  Job() : exit(false) {}
  ~Job() {}
  void process(Data* d);
  void stop()
protected:
  void run();
private:
  Functor functor;
  Data *data;
  QMutex mutex;
  QWaitCondition nextDataAvailable;
  bool exit;
};

void Job::process(Data *d)
{
  QMutexLocker locker(&mutex);
  data = d;
  functor = JobFactory::getFunctorForDataType(d->typeId());
  if ( isRunning() )
    nextDataAvailable.wakeAll();
  else
    start();
}

void Job::run()
{
  while (!exit)
  {
  QMutexLocker locker(&mutex);
  functor(data);
  nextDataAvailable.wait(&mutex);
  }  
}

void Job::stop()
{
   exit = true;
   nextDataAvailable.wakeAll();
}

Будет работать? Вроде будет. Не хватает только функции isBusy().
Т.е. получается, один пакет данных - один поток. А если от пятисот клиентов придет пятьсот пакетов данных - создадим 250000 потоков? Можно конечно пришедший пакет положить в общую очередь, а потом его оттуда достать. Будет работать? Вроде будет. Буду думать, короче. Граблей там разложено еще не мало.

А задача с потоками с Event Loop'ом так и остается нерешенной. Ну может, не спорьте, в жизни потребоваться ворочать миллион QObjecto'ов.
Я тут в виде прототипа набросал класс LatencyChecker, который работает по моей схеме (точней по гибридной - твоей и моей), т.е. запускает в луп событие с таймстепмом и меряет время простоя в очереди. А интервал запуска таких событий можно установить произвольный, и вся логика вынесена наружу - пусть поток сам решает, как и чего ему у себя мерять. Короче, я с ним еще поиграюсь и выложу сюда на суд общественности. Но уже завтра. А точней, наверное, в понедельник. Надо бы и отдохнуть от трудов праведных. :)


Название: Re: Нагрузка на поток
Отправлено: BRE от Февраль 26, 2010, 19:15
Писал прямо здесь, могут быть опечатки...  :)
Это наброски адаптированные для Qt.
Здесь нет увеличения количества потоков, но сама идея должна быть понятна.

Код
C++ (Qt)
class Job
{
public:
virtual ~Job() {}
 
protected:
// Функция будет выполняться в рабочем потоке
virtual void run() = 0;
 
// Прячем конструктор, что бы никому не захотелось его переопределять и выполнять в нем "тяжелые" операции
Job() {}
void setup( UserId id, const Command &cmd )
{
m_id = id;
m_cmd = cmd;
}
 
UserId m_id; // Id клиента
Command m_cmd; // Команда со всеми аргументами, параметрами, ...
 
// Дружим его с JobFactory, что бы он имел доступ к закрытым членам Job
friend class JobFactory;
};
 
// Конкретная реализация задачи
class MyJob : public Job
{
protected:
virtual void run()
{
// Имеем доступ к UserId и Command
 
// Выполняем операцию...
}
};
 
class JobFactory
{
public:
Job *build( UserId id, const Command &cmd )
{
Job *job = new MyJob; // Создаем объект-задачу в зависимости от cmd
if( job )
job->setup( id, cmd );
return job;
}
};
 

Код
C++ (Qt)
class ThreadPool
{
public:
ThreadPool( int prepareThreadNum = 50 )
{
for( int i = 0; i < prepareThreadNum; ++i )
{
JobThread *th = new JobThread( *this );
th->start();
m_threads.append( th );
}
}
 
void addJob( Job *job )
{
Q_ASSERT( job );
 
QMutexLocker lock( &m_mutex );
m_jobs.enqueue( job ); // Добавили в очередь задачу
m_cond.wakeOne(); // Будим один поток
}
 
void quit()
{
QMutexLocker lock( &m_mutex );
m_jobs.enqueue( 0 ); // Добавили в очередь нулевой указатель - признак завершения всех потоков
m_cond.wakeAll(); // Будим все потоки, что бы они завершились
 
// Дожидаемся завершения всех потоков и удаляем их объекты
}
 
private:
QMutex m_mutex;
QWaitCondition m_cond;
QQueue<Job*> m_jobs;
QList<JobThread*> m_threads;
 
friend class JobThread; // Разрешаем доступ к закрытым общим переменным.
};
 
// Внутренний класс, про него незнает никто кроме ThreadPool
class JobThread : public QThread
{
public:
JobThread( ThreadPool &pool ) : m_pool( pool ) {}
 
protected:
virtual void run()
{
for(;;)
{
QMutexLocker lock( &m_pool.m_mutex );
while( m_pool.m_jobs.empty() )
m_pool.m_cond.wait( &m_pool.m_mutex );
 
// В очереди есть задачи - проснулись или взялись за следующую задачу
// Общий мьютекс m_mutex в этот момент залочен
// Проверяем, что лежит в голове очереди, если 0, то это признак завершения (его мы из очереди не вынимаем, что бы его увидели все запущенные потоки)
if( !m_pool.m_jobs.head() )
break; // Получили пустой
 
// Вынимаем указатель на задачу из очереди
Job *job = m_pool.m_jobs.dequeue();
 
// Разблокируем мьютекс
m_pool.m_mutex.unlock();
 
// Запускаем задачу
job->run();
 
// Удаляем задачу из памяти
delete job;
}
}
 
private:
ThreadPool &m_pool;
};
 

Код
C++ (Qt)
void Client::readyRead()
{
// Определяем, читаем, ...
 
UserId id = ...;
Command cmd = ...;
Job *job = m_jobFactory.build( id, cmd );
if( job )
{
m_poolThread.addJob( job );
}
}