Про QThread
lexa - 02/Дек/2013 14:07
Несколько дней писал массогабаритный макет многопоточной программы на Qt с использованием QThread
Задача примерно такая: есть пул threads, туда снаружи поступает "задание", нужно выбрать подходящую (по некиим признакам) из простаивающих и скормить задание туда. Threads - долгоживущие, исполняют много заданий.
Имею сказать:
- Классический способ с переопределением
run()
, сном там на QWaitCondition и передачей данных через отдельный вызов с блокировкой на QMutex - ужас. Очень жаль, что этот ужас до сих пор существует в документации. Насколько я понимаю, кстати, есть некоторый шанс пропустить очередное "задание на обработку" - если пока внутри run() работают работу, передача параметров случится несколько раз. - Описанный в документации же более правильный способ (он там описан первым) - еще более странный. Если обратили внимание, там есть workerThread в Worker, еще один workerThread в Controller и Worker передается в контроллерную workerThread (
worker->moveToThread(&workerThread);
). Что этим хотели сказать - неясно. - Есть замечательный блог-пост You re doing it wrong , только как делать right - написано, на мой вкус, невнятно (а ссылка на старую запись - битая, впрочем вот верная, но и она помогает плохо.
- О счастье, есть правильная идея (не в документации Qt): How To Really, Truly Use QThreads; The Full Explanation. Оно немножко недоделано под мой случай (т.к. предполагается, что Worker делает одну задачу и завершается), но в сочетании с вот этим текстом становится совсем хорошо (из последнего текста берем про
QMetaObject::invokeMethod
, чтобы не делать лишних connect(), которые нафиг не нужны).
class Worker: public QObject {
Q_OBJECT;
//.. конструктор, то-се...
public slots:
void do_work(input params);
signals:
void work_done(output params);
};
void Worker::do_work(input params) {
//...
emit work_done(output params);
}
// Инициализация с вызывающей стороны:
QThread *thread = new QThread;
Worker * worker = new Worker; // QObject без родителя!
worker->moveToThread(thread);
connect(worker,SIGNAL(work_done),this,SLOT(receive_slot),Qt::QueuedConnection);
// Eще можно thread->finished() прицепить к worker->deleteLater
// Запускаем thread:
thread->start();
// Собственно работа
// Вызываем:
QMetaObject::invokeMethod(worker,"do_work",Qt::QueuedConnection,Q_ARG(....))
// Ответ получим в this->receive_slot(..)
// Можно сконнектить какой-то сигнал this с worker.do_work() и делать то же самое через emit, но это лишняя сущность.
// Склеиваем ласты
thread->quit();
thread->wait();
Q_OBJECT;
//.. конструктор, то-се...
public slots:
void do_work(input params);
signals:
void work_done(output params);
};
void Worker::do_work(input params) {
//...
emit work_done(output params);
}
// Инициализация с вызывающей стороны:
QThread *thread = new QThread;
Worker * worker = new Worker; // QObject без родителя!
worker->moveToThread(thread);
connect(worker,SIGNAL(work_done),this,SLOT(receive_slot),Qt::QueuedConnection);
// Eще можно thread->finished() прицепить к worker->deleteLater
// Запускаем thread:
thread->start();
// Собственно работа
// Вызываем:
QMetaObject::invokeMethod(worker,"do_work",Qt::QueuedConnection,Q_ARG(....))
// Ответ получим в this->receive_slot(..)
// Можно сконнектить какой-то сигнал this с worker.do_work() и делать то же самое через emit, но это лишняя сущность.
// Склеиваем ласты
thread->quit();
thread->wait();
- Нет плясок с QMutex/QWaitCondition
- Два вызова do_work() будут последовательными, event loop у QThread будет их вынимать по одному из очереди.
- Все в духе Qt: QThread используется как интерфейс к потоку исполнения, собственно даже производный класс не нужен, используем QThread в чистом виде.
- Вместо одного объекта мы имеем два (делатель и интерфейс к QThread). Можно, наверное, запрятать объект типа QThread внутрь Worker и дать наружу три вызова (start/quit/wait), но при этом я не понимаю что делать в деструкторе (вариант: ничего не делать, а прицепить QThread-овый deleteLater к finished())
P.S. История с имплементацией run() известно откуда тянется. Когда-то давно QThread::run() был pure virtual и надо было писать производный класс со своей имплементацией.
Comments
Как всегда, if you want it to be done properly, do it yourse
Как всегда, if you want it to be done properly, do it yourself..
Ерунда какая-то, вроде я вчера ответил, а ответа - нет. Мен
Ерунда какая-то, вроде я вчера ответил, а ответа - нет.
Меня в этом месте удивляет то, что традиционно хорошее качество документации и примеров у Qt - в данном случае определенно дало сбой.
Впрочем, не исключено, что это вообще от погружения в проблему. Скажем, OpenGL-ные примеры теперь тоже мне кажутся странноватыми и устаревшими (и само место в Qt5 - явно переходное и недоделаное) - после того как я связкой Qt+OpenGL годик попользовался.
В C++11 есть std::thread -
В C++11 есть std::thread - для использования никакого наследования не нужно.
А ещё есть std::async - в некоторых реализациях за ним стоит пул.
auto x = std::async([]{
// crunch, crunch, crunch
return result;
});
// ...
std::cout << x.get(); // тут нам понадобилось значение
В QT вроде есть аналог std::async: QtConcurrent::run http://qt-project.org/doc/qt-4.8/qtconcurrentrun.html
он вроде тоже за кулисами имеет пул.
Не, у меня другой паттерн, не
Не, у меня другой паттерн, не как у QtConcurrent (который я тоже использую, но для другого).
Есть долгоживущий thread, куда поступают запросы, а он, исполнив их, рапортует "готово".
Стандартный (из примеров Qt) способ с деланьем этого в QThread::run и передачей параметров через засемафоренные переменные - просто крив, плох и много чего не гарантирует.