Про QThread

Несколько дней писал массогабаритный макет многопоточной программы на 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();
Какие плюшки мы получаем при таком подходе:
  1. Нет плясок с QMutex/QWaitCondition
  2. Два вызова do_work() будут последовательными, event loop у QThread будет их вынимать по одному из очереди.
  3. Все в духе Qt: QThread используется как интерфейс к потоку исполнения, собственно даже производный класс не нужен, используем QThread в чистом виде.
Минусы:
  1. Вместо одного объекта мы имеем два (делатель и интерфейс к 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 yourself..

Ерунда какая-то, вроде я вчера ответил, а ответа - нет.

Меня в этом месте удивляет то, что традиционно хорошее качество документации и примеров у Qt - в данном случае определенно дало сбой.
Впрочем, не исключено, что это вообще от погружения в проблему. Скажем, OpenGL-ные примеры теперь тоже мне кажутся странноватыми и устаревшими (и само место в Qt5 - явно переходное и недоделаное) - после того как я связкой Qt+OpenGL годик попользовался.

В 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 и передачей параметров через засемафоренные переменные - просто крив, плох и много чего не гарантирует.