Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: Akon от Сентябрь 04, 2011, 00:33



Название: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 00:33
Огромным достоинством Qt является ее интуитивный API. Приведеный ниже код, имхо, интуитивно корректный, но на деле нет - он может работать, а может и дэдлокнуться.

Код:
	QApplication app(argc, argv);

QThread thread;
thread.start();
thread.exit();
thread.wait();

return 0;

Ваши мнения?


Название: Re: Неинтуитивный QThread
Отправлено: niXman от Сентябрь 04, 2011, 00:36
человеческий фактор? ;)


Название: Re: Неинтуитивный QThread
Отправлено: LisandreL от Сентябрь 04, 2011, 03:11
а может и дэдлокнуться
И когда же он может дэдлокнуться?


Название: Re: Неинтуитивный QThread
Отправлено: Igors от Сентябрь 04, 2011, 09:41
exit завершает eventLoop, но он мог еще не быть запущен


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 09:51
Дэдлок будет, если thread.exit() будет вызван до создания эвентлупа потока. Т.е. exit() не ставит признак того, что поток должен быть завершен при создании эвентлупа. Другими словами, у пользователя потока появляется проблема - дождаться запуска эвентлупа, и только тогда вызвать exit(). Такого функционала я не нашел в кьюте (возможно, плохо искал).
Навскидку, пара рабочих вариантов:

Код:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QThread thread;
thread.start();

do thread.exit();
while (!thread.wait(0));

return 0;
}

Код:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QThread thread;
thread.start();

{
QObject* dummy = new QObject;
dummy->moveToThread(&thread);
QMetaObject::invokeMethod(dummy, "deleteLater", Qt::BlockingQueuedConnection,
QGenericArgument(), Q_ARG(QObject*, dummy));
}

thread.exit();
thread.wait();

return 0;
}


Название: Re: Неинтуитивный QThread
Отправлено: Igors от Сентябрь 04, 2011, 10:19
Другими словами, у пользователя потока появляется проблема..
Так это хорошо - может тот пользователь думать начнет (а не только пулять сигналами и всасывать Ассыстент  :))


Название: Re: Неинтуитивный QThread
Отправлено: LisandreL от Сентябрь 04, 2011, 11:09
Akon, честно говоря не удалось воспроизвести описанную вами ситуацию.
Но если уверены, что гонка возможна: http://bugreports.qt.nokia.com/
Может даже в 4.8.0 успеете.


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 11:32
Отнаследуйтесь и в наследнике сделайте паузу перед вызовом QThread::run().

Это не баг, в доках все описано. Просто неинтуитивно, имхо. Если я вызвал QThread::exit(), то мне не хочется знать, зашел ли поток в эвенлуп или еще нет. Следуя логике QThread::exit() вызов invokeMethod
Код:
	{
QObject* dummy = new QObject;
dummy->moveToThread(&thread);
QMetaObject::invokeMethod(dummy, "deleteLater", Qt::BlockingQueuedConnection,
QGenericArgument(), Q_ARG(QObject*, dummy));
}
тоже можно послать, т.к. эвентлупа еще нет. Однако, вызов не теряется! На этом и основан мой второй вариант.

Да и сорец, имхо, неинтуитивный :)
Код:
void QThread::exit(int returnCode)
{
    Q_D(QThread);
    QMutexLocker locker(&d->mutex);
    d->data->quitNow = true;
    for (int i = 0; i < d->data->eventLoops.size(); ++i) {
        QEventLoop *eventLoop = d->data->eventLoops.at(i);
        eventLoop->exit(returnCode);
    }
}
Я, когда писал код, поверхностно просмотрел QThread::exit() и тупо купился купился на d->data->quitNow = true;  :)


Название: Re: Неинтуитивный QThread
Отправлено: LisandreL от Сентябрь 04, 2011, 16:51
Отнаследуйтесь и в наследнике сделайте паузу перед вызовом QThread::run().
Может в этом и проблема, а не в QThread?

Вообще вызов exit до exec проблемы не вызывает:

Код:
class Thread : public QThread
{
public:
    void run()
    {
        sleep( 2 );
        qDebug( "exec" );
        exec();
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Thread thread;
    thread.start();
    qDebug( "start" );
    thread.exit();
    qDebug( "exit" );
    thread.wait();
    return 0;
}


Цитировать
start
exit
exec


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 17:05
Блокировка в wait(). Сделайте sleep(1000), sleep(2) это ничто по сравнению с ку-дебагами.


Название: Re: Неинтуитивный QThread
Отправлено: LisandreL от Сентябрь 04, 2011, 17:11
Сделайте sleep(1000)
sleep(1000) - это 1000 секунд, т.е. 16,(6) минут.
Я конечно могу подождать, но...

UPD ну и даже при паузе в 17 минут ничего не дэдлочится (но 17 минут прождать приходится, разумеется).


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 17:38
Что у вас за sleep()? Время в миллисекундах.
...
И правда, void QThread::sleep ( unsigned long secs )  в секундах  :)


Название: Re: Неинтуитивный QThread
Отправлено: BRE от Сентябрь 04, 2011, 17:41
Что у вас за sleep()? Время в миллисекундах.
void QThread::sleep ( unsigned long secs ) [static protected]
void QThread::usleep ( unsigned long usecs ) [static protected]
void QThread::msleep ( unsigned long msecs ) [static protected]

Аааа, это про вендовый Sleep...?


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 17:51
Цитировать
Аааа, это про вендовый Sleep...?
Я просто был уверен, что QThread::sleep() в миллисекундах. Забыл, когда последний раз им пользовался.


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 04, 2011, 17:58
2LisandreL:
У вас программа не завершается. Вы напишите дебаг после thread.wait().


Название: Re: Неинтуитивный QThread
Отправлено: LisandreL от Сентябрь 04, 2011, 18:55
2LisandreL:
У вас программа не завершается. Вы напишите дебаг после thread.wait().
ORLY?

Код:
class Thread : public QThread
{
public:
    void run()
    {
        sleep( 10 );
        qDebug( "exec" );
        exec();
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    Thread thread;
    thread.start();
    qDebug( "start" );
    thread.exit();
    qDebug( "exit" );
    thread.wait();
    qDebug( "wait" );
    return 0;
}

Цитировать
Запускается C:\Test\untitled-build-desktop\release\untitled.exe...
start
exit
exec
wait
C:\Test\untitled-build-desktop\release\untitled.exe завершился с кодом 0

Может описанная вами проблема платформозависимая?


Название: Re: Неинтуитивный QThread
Отправлено: Igors от Сентябрь 05, 2011, 15:22
Ладно, прильнем к первоисточнику

Исходники 4.5.2 (которыми я до недавнего времени пользовался)
Код
C++ (Qt)
int QThread::exec()
{
   Q_D(QThread);
   d->mutex.lock();
   d->data->quitNow = false;
   QEventLoop eventLoop;
   d->mutex.unlock();
   int returnCode = eventLoop.exec();
   return returnCode;
}
 
Да, возможен deadlock как говорит Akon. Однако в asm (статик сделать все руки не доходят) видно что код другой. Обновляю исходники до 4.7.4

Код
C++ (Qt)
int QThread::exec()
{
   Q_D(QThread);
   QMutexLocker locker(&d->mutex);
   d->data->quitNow = false;
   if (d->exited) {
       d->exited = false;
       return d->returnCode;
   }
   locker.unlock();
 
   QEventLoop eventLoop;
   int returnCode = eventLoop.exec();
 
   locker.relock();
   d->exited = false;
   d->returnCode = -1;
   return returnCode;
}
 
Теперь соответствует

Итого: было исправлено


Название: Re: Неинтуитивный QThread
Отправлено: Akon от Сентябрь 05, 2011, 15:51
Пример я гнал на 4.6.1. Там код как в 4.5.2.
Кто знает, с какой версии пошло изменение?