Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: Авварон от Март 07, 2011, 01:22



Название: suggestion
Отправлено: Авварон от Март 07, 2011, 01:22
Давно не лазил на форуме, заглянул в этот раздел и ужаснулся. Почему все поголовно используют moveToThread(this); ??? Это же неправильно. Может прикрепить тему с линкой на блог, к-ую я уже задолбался кидать, почему это неверно и как надо писать?


Название: Re: suggestion
Отправлено: merke от Март 07, 2011, 05:35
Я к примеру использую
Код:
thread->moveToThread(thread);


Название: Re: suggestion
Отправлено: Sahab от Март 07, 2011, 11:42
давай раскроешь эту тему более полнее.


Название: Re: suggestion
Отправлено: Авварон от Март 07, 2011, 13:34
который раз кидаю линк:)
http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/

В кратце содержание:
В доке кьюта есть ошибка касательно примера с кьютредом - они его сабклассят. Эта "ошибка" существует еще с тех пор, когда у QThread не было дефолт имплементации метода run() (который ныне содержит вызов exec()). Собсно об этом был другой блог (http://labs.qt.nokia.com/2006/12/04/threading-without-the-headache/) - можно ли заменить pure virtual ф-ию на virtual без слома бинари совместимости. Можно. Итого имеем - метод run() вызывает exec(), а в примере делается сабклассинг и о5 же делается exec() для сигналов/слотов.
Почему неверно сабклассить - наследование нужно для расширения функционала самого класса. Мы расширяем кьютред? Нет. Мы добавляем функционал, к-й в общем случае к самому объекту тред не имеет отношения. Посему нужно использовать аггрегацию. Начал расписывать словами, понял, что кодом проще:
Код:
class Worker : public QObject
{
    Q_OBJECT
public slots:
    void work();
signals:
    void finished();
};

void Worker::work()
{
    // do some work
    emit finished();
}

int main()
{
    QThread thread;
    Worker w;
    w.moveToThread(&thread);
    QObject::connect(&thread, SIGNAL(started()), &w, SLOT(work()));
    QObject::connect(&w, SIGNAL(finished()), &thread, SLOT(quit()));
    thread.start();
}

Плюсы этого метода - можно делать много объектов в 1м треде, как и много экземпляров 1го класса в разных тредах. Ну и никакого сабклассинга - наш класс содержит _только_ нашу логику и не пересекается с тредом. Ну и еще нет взрыва мозга новичкам зачем тред переносить в себя (да, так можно писать, но надо четко понимать что и зачем а не рандомить "без му ту тред не работало, написал мэджик строку, заработало")

Вольный перевод окончен:)
Можете пинать.


Название: Re: suggestion
Отправлено: Авварон от Март 07, 2011, 13:50
Гы, кстати по 2й линке пример еще и зависание должен вызвать. Коннект-то директ будет, тк треды еще не назначены.
(или кьют поменяет тип авто коннекта при муве? О_о)


Название: Re: suggestion
Отправлено: Akon от Март 07, 2011, 14:07
moveToThread() иногда используется для сокетов входящих соединений, для "выема" сокета из исходного потока слушающего сокета.

А так все по делу, конечно.


Название: Re: suggestion
Отправлено: Igors от Март 07, 2011, 14:19
Гы, кстати по 2й линке пример еще и зависание должен вызвать. Коннект-то директ будет, тк треды еще не назначены.
(или кьют поменяет тип авто коннекта при муве? О_о)
auto "расшифровывается" в момент посылки сигнала, поэтому зависания не будет.
Насчет "субклассить" - как правило все равно придется, т.к. "(приватные) данные этой нитки" нужны очень часто (если не всегда).

А вообще Ваше желание обобщить мне симпатично. Вот др. вещь с которой я сталкивался: Qt использует "fair" мутекс (т.е. который "честно" усыпляет нитку - на базе pthread_cond_wait). Это может быть катастрофически медленно во многих случаях и разумной альтернативы в Qt не видно (поправьте если не так)


Название: Re: suggestion
Отправлено: Авварон от Март 07, 2011, 15:41
Гы, кстати по 2й линке пример еще и зависание должен вызвать. Коннект-то директ будет, тк треды еще не назначены.
(или кьют поменяет тип авто коннекта при муве? О_о)
auto "расшифровывается" в момент посылки сигнала, поэтому зависания не будет.
Насчет "субклассить" - как правило все равно придется, т.к. "(приватные) данные этой нитки" нужны очень часто (если не всегда).

А вообще Ваше желание обобщить мне симпатично. Вот др. вещь с которой я сталкивался: Qt использует "fair" мутекс (т.е. который "честно" усыпляет нитку - на базе pthread_cond_wait). Это может быть катастрофически медленно во многих случаях и разумной альтернативы в Qt не видно (поправьте если не так)

Ну насчет данных нитки можно поспорить:) Когда как.

Насчет мьютекса - если честно не знаю, мне обычно треды нужны были "чтоб гуй не зависал" - то есть некая работа, скорость к-ой особо не нужна, посему не заморачивался - ставил мьютексы, работало:) Скорость не профилировал.

moveToThread() иногда используется для сокетов входящих соединений, для "выема" сокета из исходного потока слушающего сокета.

А так все по делу, конечно.
Ну никто не говорит, что moveToThread не нужен. Не нужно саму нитку перемещать в ее контекст.


Название: Re: suggestion
Отправлено: Sahab от Март 07, 2011, 22:35
Цитировать
moveToThread() иногда используется для сокетов входящих соединений, для "выема" сокета из исходного потока слушающего сокета.

Цитировать
void QTcpServer::incomingConnection ( int socketDescriptor ) [virtual protected]
...
Note: If you want to handle an incoming connection as a new QTcpSocket object in another thread you have to pass the socketDescriptor to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.


Название: Re: suggestion
Отправлено: Sahab от Март 08, 2011, 00:30
2 Авварон
а Вы попробуйте в Вашем примере добавить в Worker тот же таймер.


Название: Re: suggestion
Отправлено: Авварон от Март 10, 2011, 11:48
а в чем проблема? надо только старт дергать из потока же ж, не?


Название: Re: suggestion
Отправлено: vunder от Март 11, 2011, 10:47
Мои некоторые заметки по поводу this->moveToThread(this)
Я использую эту строку, потому как без нее мой код работает "криво".
Немного описания. Мне нужен был класс, который бы из TCP-потока читал xml-данные и парсил из. Сокет создавался через new QTCPSocket(this), потому как это только базовый класс, 2 его потомка по-разному создаею сокеты: первый коннектится по адресу, второй использует SocketDescriptior. Кроме того, указатель на сокет нужен, т.к. в базовом классе есть общие методы по отправке данных по сокету.
Более того, без this->moveToThread(this) у меня не срабатывает сигнал error(QAbstractSocket::SocketError):
Цитировать
QObject::connect: Cannot queue arguments of type 'QAbstractSocket::SocketError'
(Make sure 'QAbstractSocket::SocketError' is registered using qRegisterMetaType().)
Эту проблему я решил добавлением строки
Код
C++ (Qt)
   qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
Но посыпались проблемы с таймером. Я использую startTimer() и killTimer(). Вызываются они в методе run(), т.е. в контексте потока (о чем свидетельствовала отладочная информация в QtCreator на закладке "Потоке", а также qDebug() << this->currentThreadId()). Однако, событие timerEvent(QTimerEvent *event) срабатывало в контексте главного процесса.
Также я заметил, что и слоты, которые я повесил на сокет, также срабатывают в контексте главного потока.
Также, в методе run() создается и сам сокет:
Код
C++ (Qt)
   fSocket = new QTcpSocket(this);
Однако, на это я получаю ошибку
Цитировать
QObject: Cannot create children for a parent that is in a different thread.
(Parent is UnpClientProtocol(0x12d5cd0), parent's thread is QThread(0x10ebd40), current thread is UnpClientProtocol(0x12d5cd0)
Т.е. предок объекта (QThread) находится в другом потоке, поэтому так нельзя.

Вот такие вот "пироги".


Название: Re: suggestion
Отправлено: Авварон от Март 11, 2011, 16:17
сам таймер пробовали перемещать в другой тред?


Название: Re: suggestion
Отправлено: vunder от Март 12, 2011, 12:56
сам таймер пробовали перемещать в другой тред?
Что значит в "другой"? Я использую методы startTimer() и killTimer(), которые есть у каждого наследника QObject


Название: Re: suggestion
Отправлено: Авварон от Март 14, 2011, 08:13
Ставлю 10 р что встроенный таймер тоже проверяет тред в к-ом работает. Попробуйте таки сделать мув ту тред. Ну и бтв мой пример _ничем_ не отличается от способа работы когда наследуешься от треда - ровно те же объекты будут перенесены в тред. Иногда имеет смысл делать слот init() к-ый надо дергать в контексте треда и там создавать нужны объекты.


Название: Re: suggestion
Отправлено: vunder от Март 14, 2011, 09:05
Замена
Код
C++ (Qt)
fSocket = new QTcpSocket(this);
на
Код
C++ (Qt)
fSocket = new QTcpSocket();
fSocket->moveToThread(this);
На на что не влияет. Результат одинаковый. Помогает только thread->moveToThread(thread)


Название: Re: suggestion
Отправлено: Авварон от Март 14, 2011, 09:24
этого кода мало. если вы сокет используете из методов треда, то естесственно тред надо перемещать.


Название: Re: suggestion
Отправлено: kuzulis от Март 14, 2011, 21:22
неправда! Если так:
Код
C++ (Qt)
Thread::run()
{
...
fSocket = new QTcpSocket();
...
}
 
то QTcpSocket будет в контексте потока создан, и ничо мувить не нужно
а если так:
Код
C++ (Qt)
Thread::run()
{
...
fSocket = new QTcpSocket(this);
...
}
 
то в главном потоке. И так вообще-то буит матюгатся.


Название: Re: suggestion
Отправлено: vunder от Март 14, 2011, 21:42
А как на счет
Цитировать
QObject::connect: Cannot queue arguments of type 'QAbstractSocket::SocketError'
(Make sure 'QAbstractSocket::SocketError' is registered using qRegisterMetaType().)
ЧЗХ?


Название: Re: suggestion
Отправлено: BRE от Март 14, 2011, 21:47
ЧЗХ?
На форуме есть поиск!!!
http://www.prog.org.ru/topic_11103_0.html


Название: Re: suggestion
Отправлено: CL0NE от Апрель 06, 2011, 03:19
К четвертому сообщению могу добавить пару линков
http://blog.exys.org/entries/2010/QThread_affinity.html
http://blog.exys.org/entries/2010/thread_abuse.html

А для себя хотел бы прояснить несколько моментов.
Имеем, к примеру, следующий код:
Код
C++ (Qt)
class HeavyJob : public QObject
{
   Q_OBJECT
public:
   explicit HeavyJob(QObject *parent = 0);
 
   bool isRunning();
 
public slots:
   void start();
   void stop();
 
private:
   bool running;
};
Код
C++ (Qt)
HeavyJob::HeavyJob(QObject *parent) :
   QObject(parent),
   running(false)
{
}
 
bool HeavyJob::isRunning()
{
   return running;
}
 
void HeavyJob::start()
{
   if (running)
       return;
 
   running = true;
   qDebug() << "started";
   while(running)
   {
       for(int i = 0; i < 1000; ++i);
   }
   qDebug() << "stopped";
}
 
void HeavyJob::stop()
{
   qDebug() << "stop()";
   running = false;
}
 
Где-то в GUI:
Код
C++ (Qt)
HeavyJob *job = new HeavyJob();
   QThread *worker = new QThread(this);
   job->moveToThread(worker);
   worker->start();
Код
C++ (Qt)
if (!job->isRunning())
   {
//     job->start(); // 1
       QTimer::singleShot(10, job, SLOT(start())); // 2
   }
   else
   {
       job->stop(); // 3
//        QTimer::singleShot(10, job, SLOT(stop())); // 4
   }
1) Вызываем в контексте текущего потока, не Ъ!
2) Слот отработает в отдельном потоке. Гут.
Вроде бы ясно, а вот дальше
3) Отрабатывает как надо, останавливая поток.
4) Не отправляется, и, соответственно, цикл не останавливается.
Ведь event loop в потоке свой есть, но, я так понимаю, что после получения сигнала, event loop потока ожидает исполнения тела слота, которому назначалось данное событие, чтобы продолжить обработку следующих событий. И следует добавить processEvents() в тело слота. Вот кто бы подтвердил или опровергнул данные утверждения..


Название: Re: suggestion
Отправлено: vunder от Апрель 08, 2011, 19:18
Вопрос к гуру.
Топик убедил меня в отсутствие необходимости наследоваться от QThread. Я попытался перевести свой проект на эту технологию, но пока не очень выходит.
Вообще задача такая. Клиент пытается подключиться к серверу. Если не получается подключиться, то запускается таймер ожидания, по истечении которого производится новая попытка подключиться. Данные - xml-документы. Поток данных может быть очень большой, поэтому чтение и обработка обязательно должны быть в отдельном потоке.
Я сделал следующее: объявил класс-наследник от QObject, в классе объявлены члены QTcpSocket socket и Qthread thread. В конструкторе класса делаю
Код
C++ (Qt)
this->moveToThread(&thread);
thread.start();
Если сокет не переносить в поток socket.moveToThread(&thread), то не читаются данные, не скаратывает слот сигнала readyRead, в отладке пишет что-то про QSocketNotifier. Кроме того, приходиться использовать сигнал-слот, чтобы подключаться к серверу, т.к. сокет и мой объект находятся в разных потоках.
Если сокет перенести также в поток, то данные нормально читаются и нет необходимости подключаться через вызов сигнала, можно напрямую socket.connectToHost(), но возникают другие проблемы. При выходе их программы получаю ASSERT, что нельзя вызвать сигнал для объектов из разных потоков.
Весь код привести не могу по 2-м причинам: во-первых, его "нет под рукой", а, во-вторых, часть его утеряна, т.к. для решения других задач пришлось следать откат, но желание разобраться в проблема осталось.


Название: Re: suggestion
Отправлено: CL0NE от Апрель 10, 2011, 04:06
Самое интересное, что касательно использования агрегации QThread вместо наследования, то в самом фреймворке и в ассистенте наблюдается противоречие, например, QFileInfoGatherer является наследником QThread. Этот подход и Шлее в своей книге толкает.


Название: Re: suggestion
Отправлено: like-nix от Апрель 10, 2011, 14:17
У меня складывается впечатление что заметка в блоге должна называться "we doing it wrong".=) Сори за оффтоп.


Название: Re: suggestion
Отправлено: SASA от Апрель 10, 2011, 15:04
Про агрегацию и наследование.
Например, поток делает периодически какие-то действия. Делает, ждёт, делает ...
Так вот, ждать лучше всего sleep'ом. А он защищенный у потока. Так что приходиться наследоваться.


Название: Re: suggestion
Отправлено: Авварон от Апрель 10, 2011, 15:58
таймер спасет отца русской демократии


Название: Re: suggestion
Отправлено: SASA от Апрель 10, 2011, 16:26
таймер спасет отца русской демократии
Как?


Название: Re: suggestion
Отправлено: like-nix от Апрель 10, 2011, 17:17
Я так делал.

Код:
int m_timeInterval = 100;
QTime m_elapsedTime;
m_elapsedTime.start();

if(m_elapsedTime.elapsed() > m_timeInterval)
{
m_elapsedTime.start();
m_elapsedTime.addMSecs(m_timeInterval);
//do smthn
}


Название: Re: suggestion
Отправлено: Авварон от Апрель 10, 2011, 17:34
периодически дергать слот не?


Название: Re: suggestion
Отправлено: like-nix от Апрель 10, 2011, 18:07
нужно запускать евент луп =)


Название: Re: suggestion
Отправлено: Akon от Апрель 10, 2011, 18:32
Про агрегацию и наследование.
Например, поток делает периодически какие-то действия. Делает, ждёт, делает ...
Так вот, ждать лучше всего sleep'ом. А он защищенный у потока. Так что приходиться наследоваться.


Верно подмечено. Когда функция потока реализует блокирующее поведение (например, сокеты блокирующие и т.п.), sleep находит применение. В такой реализации никакого эвентлупа нет.


Название: Re: suggestion
Отправлено: CL0NE от Апрель 10, 2011, 20:10
А
Код
C++ (Qt)
int QObject::startTimer ( int interval )
не подходит для замены? хотя все равно event loop.


Название: Re: suggestion
Отправлено: Akon от Апрель 10, 2011, 20:48
таймеру нужен эвентлуп


Название: Re: suggestion
Отправлено: Авварон от Апрель 10, 2011, 20:51
у вас неприязнь слова луп? Тред и так по дефолту начинает его крутить


Название: Re: suggestion
Отправлено: CL0NE от Апрель 10, 2011, 23:36
Дополнение к линкам - http://developer.qt.nokia.com/wiki/ThreadsEventsQObjects там же есть незавершенный русский перевод. На хабре есть полный русский ( http://habrahabr.ru/blogs/qt_software/115830/ http://habrahabr.ru/blogs/qt_software/115832/ )


Название: Re: suggestion
Отправлено: Bepec от Март 16, 2012, 19:00
Вопрос интересный по теме. Если есть необходимость в классе(которому необходим другой поток) использовать таймеры, то как возможно отказаться от "наследования от QThread", ведь таймер созданный в 1 потоке не может быть остановлен в ином. Так же таймер не переносится из потока в поток?

PS предлагать таймеры создавать динамически отдельным слотом не предлагать ;) "кучи указателей" мне не нужны.


Название: Re: suggestion
Отправлено: vbi от Март 16, 2012, 23:11
Вопрос интересный по теме. Если есть необходимость в классе(которому необходим другой поток) использовать таймеры, то как возможно отказаться от "наследования от QThread", ведь таймер созданный в 1 потоке не может быть остановлен в ином. Так же таймер не переносится из потока в поток?

PS предлагать таймеры создавать динамически отдельным слотом не предлагать ;) "кучи указателей" мне не нужны.
Почему нельзя остановить таймер в ином потоке? Можно ведь через сигнально-слотовое соединение.


Название: Re: suggestion
Отправлено: Bepec от Март 20, 2012, 07:47
А ты будешь создавать отдельную функцию под остановку/запуск каждого таймера? Или тебе это удобно?
Особено с учётом того, что таймер созданный в основном потоке, в нём и продолжает выполняться. Приводя к неизбежным тормозам в вычислениях.