Russian Qt Forum
Ноябрь 23, 2024, 06:38 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 2 3 [4] 5   Вниз
  Печать  
Автор Тема: Нагрузка на поток  (Прочитано 35315 раз)
ритт
Гость
« Ответ #45 : Февраль 25, 2010, 15:16 »

Цитировать
Вообще, сам класс ConnectionManager (у меня называется CConnectionDispatcher) хранит QMap<CUserId, CClientConnection*>, где CUserId = login+peeraddress, обновляет его при коннектах-дисконнектах и предоставляет кому угодно ссылки на объекты-подключения.
т.о. каждый CClientConnection имеет указатель на CConnectionDispatcher и может попросить у него указатель или список указателей на другие CClientConnection'ы, у которых нет публичных членов? и полученный указатель на CClientConnection в другом CClientConnection будет использоваться для того, чтобы отправить этому объекту событие? правильно?
Записан
BRE
Гость
« Ответ #46 : Февраль 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 ).
Записан
kramer3d
Гость
« Ответ #47 : Февраль 25, 2010, 17:43 »

т.о. каждый CClientConnection имеет указатель на CConnectionDispatcher и может попросить у него указатель или список указателей на другие CClientConnection'ы, у которых нет публичных членов? и полученный указатель на CClientConnection в другом CClientConnection будет использоваться для того, чтобы отправить этому объекту событие? правильно?
Не совсем. CClientConnection ничего не знает ни про другие объекты-подключения, ни про CConnectionDispatcher. Общаются они опосредованно, через иерархию классов-обработчиков данных. Просто я не указывал это звено для краткости изложения. Тем более, что к моему вопросу это никакого отношения не имеет. Эти обработчики - это легкие псевдо-функторы (я функторы не люблю, они запутывают), которые вызываются объектом-подключением после десериализации данных. Они принимают на вход данные (каждому типу данных поставлен в соответствие обработчик) и контекст, в котором эти данные были получены (в данном случае - пришли из сокета), и принимают решение, что с этими данными делать - то ли отправить бэкэнду на обработку, то ли отправить их напрямую другому объекту-подключению, а может, еще чего-нибудь. Т.е. единственная причина для объекта-подключения, по которой он может захотеть связаться с другим таким же объектом - это то, что ему пришли данные, которые этого требуют. Но это решение, как и собственно отправку события, осуществляет не сам объект, а обработчик данных. Не знаю, может быть терминология не совсем верна - по-анлийски он назвается data handler.
Записан
kramer3d
Гость
« Ответ #48 : Февраль 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 и отдать подключение ему?
Записан
ритт
Гость
« Ответ #49 : Февраль 25, 2010, 18:10 »

ну, а чем вариант в http://www.prog.org.ru/index.php?topic=12552.msg79866#msg79866 не устраивает?
Записан
BRE
Гость
« Ответ #50 : Февраль 25, 2010, 18:18 »

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

За навязчивые советы прости...  Подмигивающий
Записан
BigZ
Гость
« Ответ #51 : Февраль 25, 2010, 18:33 »

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

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

Я с самого начала треда предлагаю на поток(и) фронтэнда возложить единственную функцию отправку/получение данных, всю остальную обработку проводить в других потоках и эти варианты оценки как раз для этого метода.
Ты же хочешь в этих потоках еще и работу работать. А это может остановить обслуживание всех клиентов из этим потоком. Кстати об этом уже писалось.  Улыбающийся
Почему ты хочешь в этих потоках работу работать?
Записан
kramer3d
Гость
« Ответ #54 : Февраль 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 (UPD: матушка моя, как же тут ссылки-то вставлять?). Я, конечно, посмотрю, но врядли она мне сможет предложить что-то большее, чем предложит профайлер (см. выше почему это не совсем подходит). Тем более что привлекать стороннюю библиотеку, да еще коммерческую, да еще и от Intel, это в данном случае не совсем разумно. Благо она опенсорсная, так что я подсмотрю, какие решения там используются, и если найду что-нибудь подобающее, отчитаюсь здесь.
« Последнее редактирование: Февраль 25, 2010, 19:12 от kramer3d » Записан
kramer3d
Гость
« Ответ #55 : Февраль 25, 2010, 19:18 »

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

Подожди.
Есть несколько потоков фронтэнда, которые либо принимают данные, либо отсылают их. Тогда как нагрузка на них зависит от тех заданий, которые выполняются в бэкэнде???
По-моему никак, их загрузка зависит только от трафика и ее можно прикинуть.

Цитировать
Я там определяю, есть ли для этих данных работа и как ее работать, после чего передаю их на бэкэнд. А то может ее вовсе нет, и все что надо, это эти данные передать другому клиенту. Но я не хочу, чтобы если какой-нибудь обработчик-гад (я ведь их не контролирую, точней, контролирую, но не все) решит все-таки в треде фронтэнда работу поработать, это бы свалило сервер.
А почему не передать сразу из фронтэнда в другую нить (бэкенду?) полученные данных и пусть он, не тормозя фронтэнд, будет принимать решение о этом пакете?
Записан
kramer3d
Гость
« Ответ #57 : Февраль 25, 2010, 19:50 »

А почему не передать сразу из фронтэнда в другую нить (бэкенду?) полученные данных и пусть он, не тормозя фронтэнд, будет принимать решение о этом пакете?
Если класс, живущий в одной нитке, вызывает метод класса, живущего в другой нитке (или не живущего нигде, если он не QObject, не важно) выполнение этого метода будет происходить в треде функции, вызвавшей метод (забудем пока о том, что это небезопасно). Т.е. для передачи данных в другую нитку для принятия решений об их обработке мне нужно обработчики сделать наследниками QObject и держать для них отдельную очередь событий. Для нее встает та же проблема масштабируемости: если поток обработчиков перегружен событиями, на основании чего я должен это узнать и создать новый?
Записан
BRE
Гость
« Ответ #58 : Февраль 25, 2010, 20:14 »

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

IMHO, пропускная способность потока принятия решения, будет перекрывать все потребности фронтэнда. Ведь ему нужно будет только принять решение и запустить или не запустить другой поток для выполнения работы.
Конечно, все эти мысли без конкретных спецификаций на протокол могут быть ошибочными.
Записан
BigZ
Гость
« Ответ #59 : Февраль 25, 2010, 21:31 »

Что ж, профайлер за собой таскать? И запускать его во времени исполнения? Подмигивающий Кроме шуток, профайлер выдаст мне нагрузку потока на CPU, которая может быть стопроцентной (т.е. поток не простаивает), но при этом это не обязательно означает, что поток в моих терминах стопроцентно загружен, т.е. в него бессмысленно добавлять новые объекты для обслуживания.
Если поток в настоящий момент загружен на 100%, какое условие должно выполнится, чтобы ты считал, что в пул ещё можно добавить соединение? Ты написал, что у тебя нет информации, сколько будет выполнятся внешняя функция обработчик (из dll например или SSL шифрование). То есть получается, что задача не решаема. Даже если ты будешь знать
что в пуле только одно соединение, то всё равно не сможешь точно сказать, когда оно будет обработано и принять решение добавлять или нет.
Я собственно предлагал, экспериментальным путём при помощи профайлера получить среднее время выполнения и потом эти данные использовать.

Записан
Страниц: 1 2 3 [4] 5   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.061 секунд. Запросов: 21.