Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: deMax от Сентябрь 05, 2018, 09:51



Название: Таймер в потоке
Отправлено: deMax от Сентябрь 05, 2018, 09:51
Нужен класс, который по таймеру из другого потока отправляет данные
Интерфейс :
Код:
QPointer<Checker> checker;
connect(checker, &Checker::newData, this, &MyClass::process, Qt::BlockingQueuedConnection); // process(QMap<QString, int>), QueuedConnection - не соединяется

Накидал реализацию:
.h
Код:
class Checker: public QObject
{
    Q_OBJECT
    QThread m_thread;
    QTimer m_timer;

signals:
    void stop();

private slots:
    void started();
    void stoped();
    void timeout();

public:
    Checker();
    ~Checker();
    Q_SIGNAL void newData(QMap<QString, int>);
};

.cpp
Код:
void Checker::started()
{
    m_timer.start(1000);
}

void Checker::stoped()
{
    m_timer.stop();
}

void Checker::timeout()
{
    emit newData({});
}

Checker::Checker()
{
    this->moveToThread(&m_thread);

    m_timer.moveToThread(&m_thread);
    m_thread.start();

    connect(&m_thread, &QThread::started, this, &Checker::started);
    connect(this, &Checker::stop, this, &Checker::stoped);
    connect(&m_timer, &QTimer::timeout, this, &Checker::timeout);
}

Checker::~Checker()
{
    emit stop();
    m_thread.quit();
    m_thread.wait();
}

Вроде работает, но валидно ли? И почему QueuedConnection не соединяет? Или через потоки только POD данные пробрасывать можно?


Название: Re: Таймер в потоке
Отправлено: Авварон от Сентябрь 05, 2018, 10:42
Очень криво. НЕ НАДО обертку над тредом прокидывать в тред.


Название: Re: Таймер в потоке
Отправлено: deMax от Сентябрь 05, 2018, 12:57
Ок. А как правильно? только QTimer в другой поток выносить? И слот timeevent должен в потоке выполняться.
Еще проблема, не могу в проекте передать QMap<QString, MyStruct> с опцией QueuedConnection. Пишет: QObject::connect: Cannot queue arguments of type 'QMap<QString, MyStruct>'
Делал qRegistredMetaType, Q_DECLARE_METATYPE, все таже ошибка. Или отправлять пустой сигнал?


Название: Re: Таймер в потоке
Отправлено: Авварон от Сентябрь 05, 2018, 13:37
Ок. А как правильно? только QTimer в другой поток выносить? И слот timeevent должен в потоке выполняться.
Еще проблема, не могу в проекте передать QMap<QString, MyStruct> с опцией QueuedConnection. Пишет: QObject::connect: Cannot queue arguments of type 'QMap<QString, MyStruct>'
Делал qRegistredMetaType, Q_DECLARE_METATYPE, все таже ошибка. Или отправлять пустой сигнал?

Да, только таймер.

Тред1: Checker checker; QThread m_thread;
Тред2: QTimer m_timer.

Зачем timeout в потоке? Я бы предложил тогда сделать класс Worker и перегрузить у него timerEvent() вместо QTimer. Ну или добавить 4й объект, который будет делать работу по таймеру, но это больше boilerplate кода.

Чтобы передавалась мапа, надо зарегать тип, да; с этим может быть гемор, гугли. Возможно, MyStruct тоже надо зарегать. Ведь создается копия, значит Qt должна понимать, как скопировать тип.
Можно попробовать в QVariant завернуть, как воркэраунд.


Название: Re: Таймер в потоке
Отправлено: deMax от Сентябрь 05, 2018, 14:36
Зачем timeout в потоке? Я бы предложил тогда сделать класс Worker и перегрузить у него timerEvent() вместо QTimer. Ну или добавить 4й объект, который будет делать работу по таймеру, но это больше boilerplate кода.
В Timeout у меня "тяжелый" код, тот который должен считаться в отдельном потоке. (Ну т.е. он легкий, но в гуйне ему делать нечего)

В принципе меня и вариант QtConcurrent::run устоил бы с while(!qApp->closingDown()) { ...hard...; sleep(10) }. Только программа при закрытие ждет 10 секундный интервал, чтоб поток завершить...


Название: Re: Таймер в потоке
Отправлено: qate от Сентябрь 05, 2018, 15:50
В Timeout у меня "тяжелый" код, тот который должен считаться в отдельном потоке.

а почему просто не выкинуть рассчитанные данные их класса другого потока в класс gui без таймера ?


Название: Re: Таймер в потоке
Отправлено: deMax от Сентябрь 05, 2018, 20:29
а почему просто не выкинуть рассчитанные данные их класса другого потока в класс gui без таймера ?
У меня мониторинг постоянный, таймер нужен, но его можно и в гуйне запустить.

хорошо, есть некая функция
Код:
std::function<QList<Data>> someFunc = [](){return QStrorageInfo()....;};
и класс
Код:
class MyGuiClass: public QObject { public slot: void someMethod(QList<Data>); } 

Нужно в потоке запустить someFuncton чтобы каждые N секунд гуйня получала список файлов. мониторинг задача фоновая, как правило на отлов ошибок и не очень важная и не должна отвлекать гуйню.

Задача пустяковая и решить её можно множеством способов, как не отстрелить себе ногу, а наиболее грамотно это сделать? Ну и чтоб ошибок потенциальных, возможных крешей и падений не было.


Название: Re: Таймер в потоке
Отправлено: Old от Сентябрь 05, 2018, 21:07
Самое простое это прямо в GUI-потоке запустить таймер, из обработчика которого запускать нужную функцию через QtConcurrent::run и отслеживать ее завершение с помощью QFutureWatcher. А после завершение обновлять результат.


Название: Re: Таймер в потоке
Отправлено: Авварон от Сентябрь 05, 2018, 22:11
+1 за конкурент


Название: Re: Таймер в потоке
Отправлено: deMax от Сентябрь 05, 2018, 22:18
А создание/удаление потока? не будет ли проще поток постоянно держать открытым? Все таки не на go пишем, создание потока процедура не самая быстрая(конечно побыстрее чем лазить на диск, но все же). Или я не прав?


Название: Re: Таймер в потоке
Отправлено: sergek от Сентябрь 05, 2018, 22:30
А создание/удаление потока? не будет ли проще поток постоянно держать открытым?
Runs function in a separate thread. The thread is taken from the global QThreadPool.


Название: Re: Таймер в потоке
Отправлено: Igors от Сентябрь 07, 2018, 06:32
а почему просто не выкинуть рассчитанные данные их класса другого потока в класс gui без таймера ?
У меня мониторинг постоянный, таймер нужен, но его можно и в гуйне запустить.

хорошо, есть некая функция
Код:
std::function<QList<Data>> someFunc = [](){return QStrorageInfo()....;};
и класс
Код:
class MyGuiClass: public QObject { public slot: void someMethod(QList<Data>); } 

Нужно в потоке запустить someFuncton чтобы каждые N секунд гуйня получала список файлов. мониторинг задача фоновая, как правило на отлов ошибок и не очень важная и не должна отвлекать гуйню.

Задача пустяковая и решить её можно множеством способов, как не отстрелить себе ногу, а наиболее грамотно это сделать? Ну и чтоб ошибок потенциальных, возможных крешей и падений не было.
А зачем эту мапу вообще передавать/копировать? Сделать ее членом того же Checker'а, только защитить напр мутексом


Название: Re: Таймер в потоке
Отправлено: Авварон от Сентябрь 07, 2018, 22:40
А зачем эту мапу вообще передавать/копировать? Сделать ее членом того же Checker'а, только защитить напр мутексом

И локать мьютекс дважды. Умно!


Название: Re: Таймер в потоке
Отправлено: Igors от Сентябрь 08, 2018, 07:26
И локать мьютекс дважды. Умно!
Да собсно тут и флажком можно обойтись, без мутекса. Псевдокод
Код
C++ (Qt)
void CheckerL::timerEvent( QTimerEvent * )
{
// если получатель сигнала newData еще не сбросил флажок
// то мапа может быть в использовании, пропускаем
// (хотя и маловероятно на интервале 1 сек)
 if  (m_writeFlag) return;
 
// взводим флажок, мапа пишется
 m_writeFlag = true;
 
// строим мапу
BuildMap(m_data);
 
// посылаем сигнал в UI
emit newData(this);
}
 
Получив сигнал newData, UI использует мапу и после этого сбрасывает checker->m_writeFlag, т.е. события таймера могут заполнять ее снова


Название: Re: Таймер в потоке
Отправлено: sergek от Сентябрь 08, 2018, 11:03
Да собсно тут и флажком можно обойтись, без мутекса.
Интересно, с какими типами переменных такие операции атомарны?


Название: Re: Таймер в потоке
Отправлено: Igors от Сентябрь 08, 2018, 12:27
Интересно, с какими типами переменных такие операции атомарны?
В данном случае атомарность ни при чем, одна нитка пишет только true, другая - только false, причем писать одновременно они не могут. "Так защищаться можно"  :)


Название: Re: Таймер в потоке
Отправлено: Авварон от Сентябрь 08, 2018, 12:50
Интересно, с какими типами переменных такие операции атомарны?

Очень интересно


Название: Re: Таймер в потоке
Отправлено: sergek от Сентябрь 08, 2018, 13:14
В данном случае атомарность ни при чем, одна нитка пишет только true, другая - только false, причем писать одновременно они не могут. "Так защищаться можно"  :)
Ну, если в качестве флага вы используете int, то нельзя. Вообще-то, если я не ошибаюсь, язык не гарантирует атомарности операций записи, поэтому есть ненулевая вероятность нарваться на неприятности. Отсюда и мой вопрос.


Название: Re: Таймер в потоке
Отправлено: Igors от Сентябрь 08, 2018, 13:57
Ну, если в качестве флага вы используете int, то нельзя. Вообще-то, если я не ошибаюсь, язык не гарантирует атомарности операций записи, поэтому есть ненулевая вероятность нарваться на неприятности. Отсюда и мой вопрос.
Популярное заблуждение "атомарность = хорошо"  :) Типичная ошибка
Код
C++ (Qt)
if (writeFlag) {  
DoSomethng(..);
}
Обычно в multi-threading такие проверки - мертвому припарка. Чтение writeFlag может быть хоть 100 раз атомарно, но если др нитка его пишет - креш. Атомарность гарантирует лишь что текущее/моментальное значение будет прочитано/записано верно, но уже на момент выполнения след команды (начало DoSomethng) значение writeFlag может оказаться уже иным.

Примерим это к данному случаю
Код
C++ (Qt)
if (m_writeFlag) return;
Что произойдет если m_writeFlag было true а потом моментально стало false (UI изменило)? Ничего плохого (return), ну пропустим след опрос по таймеру т.к. предыдущий не успел. Вот если бы наоборот (было false - стало true) - тогда бы получили по дюнделю. Но в том-то и дело что UI пишет только  false (никогда true).

Предположим даже что запись m_writeFlag НЕ атомарна (напр тип double который в 32-битах может присваиваться как 2 инта). Но если пишется ноль - и такая запись не может изменить текущий "ноль" на "не ноль".


Название: Re: Таймер в потоке
Отправлено: Igors от Сентябрь 10, 2018, 11:11
Ну вот, наверное не дошло, но выяснить стесняются  :) Пожуем еще разок
Код
C++ (Qt)
// Нитка делает опрос, сохраняет рез-т в мапе (m_data)
// и посылает сигнал в главную нитку
void Checker::timerEvent( QTimerEvent * )
{
 if  (!m_writeFlag) {
  m_writeFlag = true;
  BuildMap(m_data);
  emit newData(this);
}
}
Код
C++ (Qt)
// Главная Нитка использует мапу и после этого сбрасывает флажок
void SomeWindow::SlotNewData( Checker * checker )
{
// обновляем UI используя checker->m_data
// ...
  checker->m_writeFlag = false;
}
Когда первая нитка получает событие таймера и m_writeFlag == false, значит или это стартовый ноль или главная нитка уже закончила использование предыдущей мапы, а начать новое не сможет т.к. сигнал newData исходит из первой. Поэтому без разницы как пишется флажок - атомарно или нет, если он оказался сброшен, то никакая другая нитка его уже не изменит, и мы можем смело писать новую мапу

Ну а можно и не включать моск, а просто поставить QMutexLocker (или QMutex::tryLock) перед чтением и записью.


Название: Re: Таймер в потоке
Отправлено: deMax от Сентябрь 10, 2018, 12:00
Интересно, с какими типами переменных такие операции атомарны?
std::atomic


Название: Re: Таймер в потоке
Отправлено: sergek от Сентябрь 12, 2018, 00:21
Ну вот, наверное не дошло, но выяснить стесняются  :) Пожуем еще разок
Да не, не стесняются, просто лишен был на 3 дня интернета) Да и разжевывать тут нечего, есть уже вмятина от этого садового инструмента.
Просто применять решение, которое работает для данного конкретного случая (типа, тут играем, здесь рыбу заворачивали) не здорово. Потом забудешь, для какого.
Поэтому лучше включить "моск" и делать
Ну а можно и не включать моск, а просто поставить QMutexLocker (или QMutex::tryLock) перед чтением и записью.


Название: Re: Таймер в потоке
Отправлено: Igors от Сентябрь 12, 2018, 03:33
Да не, не стесняются, просто лишен был на 3 дня интернета) Да и разжевывать тут нечего, есть уже вмятина от этого садового инструмента.
Просто применять решение, которое работает для данного конкретного случая (типа, тут играем, здесь рыбу заворачивали) не здорово. Потом забудешь, для какого.
Этот садовый инструмент называется lock-free. С чисто практической, утилитарной точки зрения - да, он заслуживает осуждения именно по причине своей сложности, мозголомности (хотя код и выглядит простым). Ошибиться очень легко, напр
Код
C++ (Qt)
// Главная Нитка использует мапу и после этого сбрасывает флажок
void SomeWindow::SlotNewData( Checker * checker )
{
// обновляем UI используя checker->m_data
// ...
  checker->m_writeFlag = false;
// ..
// здесь чем-то увлекся, забыл и сбросил флажок еще раз
// ..
  checker->m_writeFlag = false;
}
И вот уже очень мерзкий баг который вылезет не сразу

Поэтому лучше включить "моск" и делать
Ну если это "включить"  :)


Название: Re: Таймер в потоке
Отправлено: sergek от Сентябрь 12, 2018, 09:58
Ну если это "включить"  :)
Да. Потому что дальше надо много думать, чтобы избежать взаимных блокировок)))