Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: RedDog от Май 07, 2018, 20:04



Название: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Май 07, 2018, 20:04
Есть некоторое приложение, с большим количеством разнообразных логик: БД, сеть, бизнес-логики N штук, логирование 4 вида.
До некоторого времени разрабатывалось по принципу: "в любой не понятной ситуации создавай отдельный QThread и суй в него свою очередную логику".
Итого, имеем: на БД создается в районе 50 нитей (много подключений, каждое в своем потоке), на сеть около 10 (здесь пул потоков, одна нить несколько подключений обслуживает), вся остальная логика еще на полтинничек тянет.
Делалось это с той целью, что бы одна "логическая единица" не мешала и не лочила работы других, потому как сервер и должен обслуживать клиентов в почти онлайн-режиме.
Вопрос вот в чем: стоит ли продолжать в таком духе и когда пора остановится?
Про пулы потоков в курсе, но есть сложности в определении свободной нити, что бы не лочить при тяжелых вычислениях.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Май 15, 2018, 21:13
Я извиняюсь, может вопрос не так задал?
Или все же нормальная практика делать для отдельной логики отдельную нить?


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: ssoft от Май 15, 2018, 22:12
Я извиняюсь, может вопрос не так задал?
Или все же нормальная практика делать для отдельной логики отдельную нить?

Какой ответ Вы хотите получить?
Если программа работает и хорошо себя чувствует, то, возможно, такой подход оправдан, так как прост и относительно надежен. Это с практической точки зрения.
Если смотреть с технической или академической точки зрения, то чем больше потоков, тем хуже себя чувствует диспетчер ОС.
Если у Вас задачи связанны с высокой производительностью и большими нагрузками, то имхо такой подход неприемлем, лучше посмотреть в сторону корутин буста, на std::future или еще куда-нибудь.
В общем, ответ зависит от критериев разумности в рамках решения конкретной задачи.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: vic57 от Май 15, 2018, 23:03
Я извиняюсь, может вопрос не так задал?
Или все же нормальная практика делать для отдельной логики отдельную нить?
сервер по любому должен крутиться, а для БД может лучше async?
https://xydan.livejournal.com/8595.html


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Май 16, 2018, 18:48
Какой ответ Вы хотите получить?
Если программа работает и хорошо себя чувствует, то, возможно, такой подход оправдан, так как прост и относительно надежен. Это с практической точки зрения.
Если смотреть с технической или академической точки зрения, то чем больше потоков, тем хуже себя чувствует диспетчер ОС.
Если у Вас задачи связанны с высокой производительностью и большими нагрузками, то имхо такой подход неприемлем, лучше посмотреть в сторону корутин буста, на std::future или еще куда-нибудь.
В общем, ответ зависит от критериев разумности в рамках решения конкретной задачи.
Пофантазируем...
К примеру есть 8 нитей (QList<QThread>). Есть 32 объекта (SomeWorker), которые должны обрабатывать свои слоты, не мешаясь друг другу.
В начальный момент времени я по какой то логике на каждую нить вешаю по 4 объекта.
Рандомно объекты начинают нагружаться.
Настает момент времени, когда на одной нитке объекты начинают мешать друг другу, следовательно какой то из объектов желательно в соседнюю перекинуть.
Задача в том, как найти наименее нагруженную нитку?
Пока смутно маячит решение в SomeWorker делать а-ля флажок (в каждом слоте?) работает конкретный экземпляр или "курит".
Тогда зная весь массив SomeWorker-ов можно пробежаться и найти нитку с наименьшим/наибольшим кол-вом работающих.
Можно ввести "веса" нагрузки для каждого SomeWorker-а.
Тогда берем наиболее загруженный поток, выдергиваем из него N SomeWorker-ов и кидаем в наименее загруженный.
Ну вот как то так...
М.б. у кого то еще какие мысли будут.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Май 16, 2018, 18:51
а для БД может лучше async?
Каждое подключение к БД работает в своем Qt-шном потоке.
Кстати, логика работы в БД во всей архитектуре, самая быстрая получается, дольше манарегы разгребают, все что из БД выкачалось.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: Авварон от Май 16, 2018, 18:56
Идея такая - есть несколько рабочих потоков (пусть будет 4) разгребают сообщения. Если обработка занимает мало времени, то они обрабатывают напрямую и кидают сигнал дальше.
Если обработка долгая, то ставим задачу в тред пул, вешаем вотчера, асинхронно ждем сигнала от вотчера.
В целом, можно любую задачу класть в пул, но будут накладные расходы на задачах, которые проще обработать так.
Можно накрутить свой фреймворк поверх QFuture/QFutureInterface который бы исполнял задачу в том же потоке в зависимости от "веса" и сделать это универсально. Идея такая.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Май 16, 2018, 19:08
Идея такая - есть несколько рабочих потоков (пусть будет 4) разгребают сообщения.
Тут сложность в реализации, а именно 100500 логических единиц абсолютно разной направленности, с вообще не похожей архитектурой, и их слить в единый АПИ по генерации сообщений (по всей видимости однотипных), как мне видится, не реально.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: Авварон от Май 16, 2018, 19:13
void run(std::function<void()>) да поможет вам


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: Igors от Май 17, 2018, 05:53
В начальный момент времени я по какой то логике на каждую нить вешаю по 4 объекта.
Рандомно объекты начинают нагружаться.
А просто очередь задач (возможно с приоритетом)? При поступлении задачи кладете ее в очередь и сигнальчик всем ниткам. Каждая нитка достает задачу их очереди и выполняет ее, Нет задач - возвращается в run до след сигнала


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Май 17, 2018, 19:13
А просто очередь задач (возможно с приоритетом)?
Будете смеяться, но такая логика уже есть, и она сидит в своем потоке, и шлет сообщения остальным.

PS: вся сложность, что проект довольно большой, около 2000 цпп файлов, и рефакторинг, как я вижу, надо проводить "по верхам", т.е наследоваться от QThread и еще некоторый враппер объектов, которые по потокам раскидываются.
Есть некоторые мысли на этот счет, если вырастут в некий прототип, то выложу на рассмотрение.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: ssoft от Май 18, 2018, 08:31
Есть некоторые мысли на этот счет, если вырастут в некий прототип, то выложу на рассмотрение.

Если требуется рефакторинг по "верхам", то прототип тут уже известен - Worker'ы вместо Thread'ов, работающие в пуле потоков.
Оставим Worker'у свою очередь сообщений, добавим признак, что сообщение отправлено в очередь пула потоков для обработки.
Далее такая логика с соответствующими блокировками:
  • При добавлении нового сообщения Worker'у, если признак обработки сообщения выставлен, то складываем сообщение в очередь Worker'а, если нет - формируем задание на выполнение и помещаем прямиком в пул потоков. Выставляем признак, что сообщение в обработке
  • Выполняем задание.
  • После завершения обработки очередного задания пытаемся получить у Worker'а сообщение для формирования нового задания, если есть - помещаем новое задание в очередь пула (или сразу выполняем, зависит от выбранной стратегии)

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

ОДНАКО!
Большинство средств Qt реализованы таким образом, что работать с экземплярами объектов можно только в том потоке, с которым ини связаны!
Это относится к сети, GUI, м.б. к SQL и т.п. Поэтому вышеописанный подход напрямую не заработает, придется костыли выдумывать и т.п.

Если ПО нормально функционирует и обеспечивает необходимые характеристики, то мой совет - не нужно ничего менять.
Добавляйте новый функционал по новым правилам, а старый модифицируйте только по мере необходимости.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Июль 09, 2018, 19:00
Попутный вопрос возник:
Есть некий WorkerMain живущий в своем QThread-е, подписанный на 100500 различных других Worker, которые тоже по ниткам раскиданы.
Необходимо циклично обрабатывать сигналы Worker-ов, таким образом, что бы не было оверхеда в WorkerMain.
Для этого берутся все Worker-ы и испускают по одному сигналу.
Следующий сигнал они не могут испускать, пока очередь сообщений WorkerMain не станет пуста.
Вопрос вот в чем: как достучаться до очереди сообщений QThread-а и узнать, есть ли там что, или она пустая?


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: ssoft от Июль 10, 2018, 07:37
Можно использовать Qt::BlockingQueuedConnection при соединении сигнала Worker со слотом MainWorker.
Тогда в очереди будет не более одного сигнала Worker одновременно.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Июль 11, 2018, 21:24
Вопрос решился следующим образом:
переопределил eventFilter и в нем при событии QEvent::MetaCall делаю инкремент на входящие/исходящие сообщения

В псевдокоде это выглядит примерно тами образом:

Код:
bool MainWorker::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::MetaCall)
    {
        QMetaCallEvent* metaEvent = static_cast<QMetaCallEvent*>(event);
        if (nullptr != dynamic_cast<const InputWorker*>(metaEvent->sender()))
        {
            ++m_queueCounter;
        }
        else if (nullptr != dynamic_cast<const OutputWorker*>(metaEvent->sender()))
        {
            if (--m_queueCounter == 0);
                emit imFreeForJob();
        }
    }
}


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Сентябрь 03, 2018, 10:16
Заметил разное поведение приложения на разных ОСях.
При одинаковых условиях нагрузки под Вин серерером 2016 проц нагружен на 20-30%, под Дебианом на 70-80%, соответственно на последнем гораздо быстрее все работает.
Куда посмотреть?
Есть предположение, что синхронизация на мютексах под виндой медленнее работает.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: zhbr от Сентябрь 03, 2018, 14:56
может приоритеты процессов/ниток?

и в догонку: вы уверены что условия одинаковые? просто на вин сервере обычно ещё сервисы какие-то крутятся. я так понимаю под одинаковыми условиями понимается - на одном и том же физически компьютере, но в разных загруженных ОС?


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Сентябрь 03, 2018, 15:17
Исходники собраны одни и те же, насильно приоритеты ниток в коде не меняются.
Железки  физически разные, но характеристики одинаковые по ядрам и частоте. Диски с примерно одинаковой скоростью. На виндовой тачке только памяти побольше 32гига против 16гиг линуксовой.
На обоих локальный постгрес стоит с идентичными настройками.
В винде проц и диск никакие службы не грузят.


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: zhbr от Сентябрь 04, 2018, 08:15
а какие компиляторы использовались?


Название: Re: Очень многопоточная архитектура приложения.
Отправлено: RedDog от Сентябрь 04, 2018, 09:52
Под виндой студийный 2015 х64, под линем гцц 4.9.2 тоже 64.
Qt 5.8 штатной поставки с их сайта, ничего не пересобиралось.

ps: провёл тест - те же бинарники на 10-й винде, показывают такую же производительность, как и на дебиане.
Из чего можно сделать вывод, что серверная винда как то по особому распределяет контексты потоков, и/или с локерами по другому работает.
pps: от начала этой темы удалось перевести львиную долю отдельных ниток в пулы, кол-во потоков на все приложение значительно уменьшилось.