crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )

 
Ответить в данную темуНачать новую тему
> Как правильно писать многопоточное приложение, советы нубам
borune
  опции профиля:
сообщение 8.10.2014, 21:42
Сообщение #1


Участник
**

Группа: Участник
Сообщений: 152
Регистрация: 1.1.2011
Пользователь №: 2314

Спасибо сказали: 0 раз(а)




Репутация:   0  


Господа, прошу вашего совета и помощи. Ситуация следующая: пишу программу, которая общается по ком-порту с некими устройствами, и выводит на экран данные, получаемые от этих устройств (рисует графики). Одновременно к компьютеру может быть подключено несколько устройств. При подключении нового устройства должно происходить автоматическое его определение, настройка и добавление в главное окно программы дока с соответствующим графиком.

Мне кажется, тут напрашивается сделать многопоточную программу. Я попытался сделать это. Попробую описать структуру своего ПО. Имеется класс для взаимодействия программы с ком-портом (назовем его data_collector), который в бесконечном цикле пытается читать данные из порта. В случае, если устройство обнаруживается, он эмитит сигнал device_connected(). При получении новых данных от устройства он передает их при помощи сигнала data_received(QString).

В main'е создаю объекты следующим образом:
Раскрывающийся текст
int main(int argv, char **argc)
{
...
    MainWindow mainWin;                                      // создаем главное окно
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts())    // идем по списку всех установленных портов
    {
        QThread *listen_thread = new QThread();  // создаем поток
        data_collector *collector = new data_collector(info.portName().remove(0,3).toInt());  // создаем наш объект, параметр - номер порта

                // подключаем сигналы к обработчикам
        QObject::connect(collector,SIGNAL(device_connected()),&mainWin,SLOT(addDevice()));  
        QObject::connect(collector,SIGNAL(device_disconnected()),&mainWin,SLOT(disableDevice()));

        QObject::connect(listen_thread,SIGNAL(started()),collector,SLOT(connect_device()));  // соединяемся с потоком

        collector->moveToThread(listen_thread);   // помещаем наш объект в созданный поток

        listen_thread->start();   // запускаем поток
    }
...
}

Слоты MainWindow::addDevice() и MainWindow::disableDevice(), отвечают за добавление и удаление дока соответствующего устройства. Нас интересует слот addDevice, потому что именно в нем надо подключить сигнал прихода новых данных к слоту отрисовки графика. Слот реализован так:
Раскрывающийся текст
void MainWindow::addDevice()
{
    ...
    MyContainer *container = new MyContainer(this, 0); // создаем док

    data_collector *collector;
    collector = dynamic_cast <data_collector*> (sender());  // получаем указатель на наш объект
    if(!collector) return;

    connect(collector,SIGNAL(data_recieved(QString)),container,SLOT(data_recieved(QString)));  // подключаем сигнал к обработчику
    ...
}


Запускаем, все работает, графики рисуются, но отклик главного окна отсутствует напрочь - даже ALT-F4 не помогает. Проверял, действительно ли каждый data_collector запускается в отдельном QThread, отличным от QThread'а главного окна - да.

Подскажите пожалуйста, в чем может быть причина такого поведения главного окна и как сделать его нормальным.

Сообщение отредактировал borune - 9.10.2014, 6:58
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
lanz
  опции профиля:
сообщение 9.10.2014, 18:51
Сообщение #2


Старейший участник
****

Группа: Участник
Сообщений: 690
Регистрация: 28.12.2012
Пользователь №: 3660

Спасибо сказали: 113 раз(а)




Репутация:   8  


Нет отклика - не закрывается? Или что имеется ввиду?
Приложите минимальный проект.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
borune
  опции профиля:
сообщение 9.10.2014, 21:58
Сообщение #3


Участник
**

Группа: Участник
Сообщений: 152
Регистрация: 1.1.2011
Пользователь №: 2314

Спасибо сказали: 0 раз(а)




Репутация:   0  


Цитата(lanz @ 9.10.2014, 19:51) *
Нет отклика - не закрывается? Или что имеется ввиду?
Приложите минимальный проект.

Не только, нет реакции на наведение и нажатие кнопок тулбара (не появляется ToolTip, кнопка не меняет вид при наведении).

Копался весь вечер, вроде бы удалось локализовать проблему. Что выяснил:
1. Порты действительно слушаются в отдельных потоках
Как выяснял. Отрисовку графика сделал в самом главном окне (по таймеру генерировал рандомную точку и добавлял ее на график), тем самым убрал связь между главным окном и объектами класса data_collector. Затем перенес все объекты data_collector в тот же поток, где работает главное окно. Запустил, получил дикие лаги. Перенес объекты data_collector в отдельные потоки - лаги исчезли.

2. Скорее всего, дело в том, что при передаче сигнала из одного потока в другой, они преобразуются в события по какому-то хитрому правилу. Непонимание этого механизма и вызывает непонимание причины такого поведения главного окна.
Пробовал делать так. Сделал слот, который добавляет точку на график. Если вызывать этот слот при помощи QTimer::singleShot из класса MainWindow, то никаких проблем не возникает. Но если тот же самый слот вызывать по сигналу от объекта data_collector (который находится в другом потоке), то все сразу же начинает подвисать.

Может попробовать вместо сигнала из потока кидать события?

Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
lanz
  опции профиля:
сообщение 9.10.2014, 23:16
Сообщение #4


Старейший участник
****

Группа: Участник
Сообщений: 690
Регистрация: 28.12.2012
Пользователь №: 3660

Спасибо сказали: 113 раз(а)




Репутация:   8  


Цитата
Может попробовать вместо сигнала из потока кидать события?

Сигналы между потоками и так преобразуются в события.

Цитата
Скорее всего, дело в том, что при передаче сигнала из одного потока в другой, они преобразуются в события по какому-то хитрому правилу. Непонимание этого механизма и вызывает непонимание причины такого поведения главного окна.

Очень маловероятно.

С какой скоростью у вас генерятся события? Может ваши устройства просто забивают очередь? Т.е. 100% времени главный поток занимается их обработкой?

Выложите минимальный проект, который демонстрирует проблему. Пока ничего не ясно.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
borune
  опции профиля:
сообщение 10.10.2014, 6:26
Сообщение #5


Участник
**

Группа: Участник
Сообщений: 152
Регистрация: 1.1.2011
Пользователь №: 2314

Спасибо сказали: 0 раз(а)




Репутация:   0  


Цитата(lanz @ 10.10.2014, 0:16) *
Цитата
Может попробовать вместо сигнала из потока кидать события?

Сигналы между потоками и так преобразуются в события.

Цитата
Скорее всего, дело в том, что при передаче сигнала из одного потока в другой, они преобразуются в события по какому-то хитрому правилу. Непонимание этого механизма и вызывает непонимание причины такого поведения главного окна.

Очень маловероятно.

С какой скоростью у вас генерятся события? Может ваши устройства просто забивают очередь? Т.е. 100% времени главный поток занимается их обработкой?

Выложите минимальный проект, который демонстрирует проблему. Пока ничего не ясно.


попробую выложить, после того, как сделаю его минимальным.

Насчет скорости генерации событий. Если их генерить из потока, в котором работает гуй, то не зависает вообще никогда, даже если слот вызывать по singleShot'у с задержкой 0. Выполнение слота занимает 10-30 мс (в зависимости от времени работы программы: когда она только запущена, данных в графике нет, поэтому перерисовка происходит быстро, по мере набора данных время отрисовки увеличивается). Частота генерации сигналов от объекта data_collector непостоянна, может пару секунд ничего не высылать, а потом выдать сразу несколько сигналов. Ваш вариант кажется наиболее правдоподобным. Можно поставить опыт - создать еще класс, который бы просто с какой-то периодичностью высылал сигналы, аналогичные сигналам, высылаемым data_colelctor'ом, запустить его в отдельном потоке и так же соединить со слотом отрисовки графика в MainWindow, а объекты data_collector убрать. Тем самым мы уберем апериодичность формирования сигналов и вообще связь с ком-портом. Посмотреть, что из этого выйдет. Если будет также зависать, значит дело в механизме передачи событий между потоками, и тут у меня вообще нет идей, что делать. А если будет норм работать, то тогда поставить в data_colelctor'e фильтр, который бы генерировал сигналы не абы как, а с заданной периодичностью.

UPD
Поставил опыт. Если генерить сигнал с частотой 10 Гц (раз в 100 мс) - все норм. Если генерить с частотой 20 Гц (раз в 50 мс), отрисовка идет нормально, отклик на нажатие кнопок в тулбаре есть, но если при этом выплонять в гуе еще какие-то действия (например, изменять масштаб графиков), то начинает зависать. Причем зависает очень странно - напрочь. Ведь, если я правильно понимаю, когда событие изменения масштаба будет обработано, программа должна вернуться в нормальное состояние, так как никаких других событий из гуя не приходит, однако же на самом деле восстановления работоспособности программы после изменения масштаба не наблюдается, она не "отвисает".

Итак, проблема локализована. Если мои выводы верны, то формулируется она следующим образом.

При генерации сигналов из того же потока, где находится обработчик этого сигнала, сигналы корректно обрабатываются даже при максимально возможной частоте их генерации. Даже если генерировать сигналы с частотой 100 Гц, то при условии, что обработка сигнала занимает 500 (!) мс, наблюдается лишь заторможенность реакции графических элементов управления на действия пользователя. Никаких зависаний нет.

В случае же, если сигнал генерируется из другого потока, картина меняется. Если время обработки сигнала того же порядка, что частота генерации (например, обработка занимает 50 мс, частота генерации 20 Гц), то зависаний также нет, однако они начинаются при совершении любого действия в гуе. Это вполне логично, так как в этом случае у нас получается, что сигналы генерятся каждые 50 мс, и их обработка занимает ~50 мс, поэтому поток все время занимается обработкой этих сигналов, и в случае появления других событий (нажатий кнопок), не может их корректно обрабатывать. Если же оставить "запас" в несколько десятков мс, то все хорошо работает. Точных цифр дать не могу, поскольку у меня, как уже говорил, время обработки сигналов напрямую связано с частотой их генерации (чем чаще приходит сигнал, тем больше точек на графике -> дольше его рисовать).

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

Сообщение отредактировал borune - 10.10.2014, 7:45
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
lanz
  опции профиля:
сообщение 10.10.2014, 18:40
Сообщение #6


Старейший участник
****

Группа: Участник
Сообщений: 690
Регистрация: 28.12.2012
Пользователь №: 3660

Спасибо сказали: 113 раз(а)




Репутация:   8  


Цитата
Почему в идентичных, казалось бы, ситуациях (отправка сигнала из того же потока и из другого) наблюдается такое разительное отличие в поведении.

Это как раз очень легкий вопрос.
Когда событие вызывается из своего потока, генерируется просто вызов функции.
Когда из соседнего, создается объект события, в него копируются данные, захватываются мутексы на очередь событий принимающего треда, и сообщение добавляется в очередь, потом EventLoop обрабатывает его и вызывает соответствующую функцию-слот.
Далеко не идентичные ситуации, согласитесь :lol:
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
borune
  опции профиля:
сообщение 10.10.2014, 19:57
Сообщение #7


Участник
**

Группа: Участник
Сообщений: 152
Регистрация: 1.1.2011
Пользователь №: 2314

Спасибо сказали: 0 раз(а)




Репутация:   0  


Цитата(lanz @ 10.10.2014, 19:40) *
Цитата
Почему в идентичных, казалось бы, ситуациях (отправка сигнала из того же потока и из другого) наблюдается такое разительное отличие в поведении.

Это как раз очень легкий вопрос.
Когда событие вызывается из своего потока, генерируется просто вызов функции.
Когда из соседнего, создается объект события, в него копируются данные, захватываются мутексы на очередь событий принимающего треда, и сообщение добавляется в очередь, потом EventLoop обрабатывает его и вызывает соответствующую функцию-слот.
Далеко не идентичные ситуации, согласитесь :lol:

не могу не согласиться, спасибо)
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
lanz
  опции профиля:
сообщение 10.10.2014, 19:59
Сообщение #8


Старейший участник
****

Группа: Участник
Сообщений: 690
Регистрация: 28.12.2012
Пользователь №: 3660

Спасибо сказали: 113 раз(а)




Репутация:   8  


Что то вы делаете не так, вот напрмер 6 тредов с периодом 50 мс и постоянным обновлением графиков, никаких тормозов:

Прикрепленные файлы
Прикрепленный файл  scratch1.zip ( 3.55 килобайт ) Кол-во скачиваний: 115
 
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
borune
  опции профиля:
сообщение 12.10.2014, 12:04
Сообщение #9


Участник
**

Группа: Участник
Сообщений: 152
Регистрация: 1.1.2011
Пользователь №: 2314

Спасибо сказали: 0 раз(а)




Репутация:   0  


Цитата(lanz @ 10.10.2014, 20:59) *
Что то вы делаете не так, вот напрмер 6 тредов с периодом 50 мс и постоянным обновлением графиков, никаких тормозов:

я сделал так: в обработчике вызываю отрисовку графика через QTimer::singleShot. То есть отрисовка вызывается из самого гуя, поэтому никаких преобразований в события и обратно не происходит. Ничего вроде не лагает. пока)
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение

Быстрый ответОтветить в данную темуНачать новую тему
Теги
Нет тегов для показа


1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0




RSS Текстовая версия Сейчас: 17.4.2024, 2:10