- PVSM.RU - https://www.pvsm.ru -

QThread: Вы не делаете это неправильно

Эта статья об использовании QThread является ответом на другой пост «You're doing it wrong» [1] («Вы делаете это неправильно»), опубликованный три года назад, моим коллегой в то время, Брэдом.
В своём посте [1] Брэд рассказывает, что он видит, как многие пользователи наследуют класс QThread, добавляют несколько слотов и делают в конструкторе примерно так:

moveToThread(this);

Они перемещают поток сам в себя. Как говорит Брэд, это неправильно: QThread должен быть интерфейсом для управления потоком.

Слоты в объекте QThread выполняются не в том потоке, который он предоставляет, поэтому заводить слоты в потомке QThread — плохая практика.
Но далее Брэд продолжает и отрицает любое наследование от QThread вообще. Он утверждает, что это является нарушением надлежащего объектно-ориентированного проектирования. В этом я с ним не согласен. Размещение кода в run() является допустимым объектно-ориентированным способом расширить QThread: этот класс представляет собой поток, который запускает цикл обработки событий, а его потомок — поток, который раскрывает то, что нужно сделать в run().
После прочтения поста Брэда некоторые участники сообщества «отправились в крестовый поход» против наследования QThread. Проблема в том, что существует много вполне обоснованных причин для создания потомка QThread.
В Qt 5.0 и Qt 4.8.4 документация QThread изменилась и теперь пример кода не связан с наследованием. Посмотрите на первый пример использования QThread в документации Qt 4.8 [2]. Он содержит множество строк шаблонного кода, чтобы просто запустить код в потоке. И там даже имеется утечка: QThread никода не будет завершён и уничтожен.
Однажды в IRC мне задал вопрос пользователь, который следовал этому примеру для того, чтобы запустить свой код в потоке. Он потратил много времени, пытаясь выяснить, как правильно уничтожить поток. Именно это побудило меня написать эту запись в блоге.
Если вы допускаете наследование QThread, вот что вы должны получить в итоге:

class WorkerThread : public QThread {
    void run() {
        // ...
    }
};

void MyObject::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread;
    connect(workerThread, SIGNAL(finished()),
            workerThread, SLOT(deleteLater()));
    workerThread->start();
}

Этот код не содержит утечки, гораздо проще и содержит меньше накладных расходов, так как здесь не создаётся бесполезный объект.
Другой пример использования многопоточности в Qt threadedfortuneserver [3] использует этот шаблон для запуска блокирующих операций и он гораздо проще чем аналогичный, использующий worker object.
Я внёс исправление в документацию [4] для того, чтобы пользователей больше не отговаривали наследовать QThread.

Когда следует наследоваться, а когда нет?

  • Когда вам не нужен цикл обработки событий в вашем потоке, следует создать потомка QThread.
  • Если вам нужен цикл обработки событий и обработка сигналов и слотов внутри потока, наследоваться не стоит.

Как насчёт использования вместо этого QtConcurrent?

QThread является весьма низкоуровневым поточным примитивом, поэтому будет лучше, если вы воспользуетесь QtConcurrent.
Но QtConcurrent обладает собственными недостатками: он связан с единым пулом потоков и поэтому его использование не является хорошим решением если вы хотите запустить блокирующую операцию. Кроме того, он содержит некоторые проблемы в своей реализации, что может отрицательно сказаться на производительности. Всё это поправимо. Возможно, уже даже в Qt 5.1 появятся некоторые улучшения.
Хорошей альтернативой является стандартная библиотека C++11 [5] с std::thread [6] и std::async [7] которые в настоящий момент являются стандартным способом запуска кода в отдельном потоке. Положительный момент в том, что они отлично работают с Qt: все остальные потоковые примитивы Qt могут использоваться с нативными потоками (Qt автоматически создаст QThread, если потребуется).

Автор: epicfailguy93

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/qt-2/25723

Ссылки в тексте:

[1] «You're doing it wrong»: http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/

[2] первый пример использования QThread в документации Qt 4.8: https://qt-project.org/doc/qt-4.8/QThread.html

[3] threadedfortuneserver: http://code.woboq.org/qt5/qtbase/examples/network/threadedfortuneserver/fortunethread.cpp.html#_ZN13FortuneThread3runEv

[4] исправление в документацию: https://codereview.qt-project.org/#change,45271

[5] C++11: http://woboq.com/blog/cpp11-in-qt5.html

[6] std::thread: http://en.cppreference.com/w/cpp/thread/thread/thread

[7] std::async: http://en.cppreference.com/w/cpp/thread/async

[8] Источник: http://habrahabr.ru/post/167009/