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

в 12:42, , рубрики: multithreading, qt, Qt Software, qt4, qt5, QThread, многопоточность, переводы

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

moveToThread(this);

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

Слоты в объекте QThread выполняются не в том потоке, который он предоставляет, поэтому заводить слоты в потомке QThread — плохая практика.
Но далее Брэд продолжает и отрицает любое наследование от QThread вообще. Он утверждает, что это является нарушением надлежащего объектно-ориентированного проектирования. В этом я с ним не согласен. Размещение кода в run() является допустимым объектно-ориентированным способом расширить QThread: этот класс представляет собой поток, который запускает цикл обработки событий, а его потомок — поток, который раскрывает то, что нужно сделать в run().
После прочтения поста Брэда некоторые участники сообщества «отправились в крестовый поход» против наследования QThread. Проблема в том, что существует много вполне обоснованных причин для создания потомка QThread.
В Qt 5.0 и Qt 4.8.4 документация QThread изменилась и теперь пример кода не связан с наследованием. Посмотрите на первый пример использования QThread в документации Qt 4.8. Он содержит множество строк шаблонного кода, чтобы просто запустить код в потоке. И там даже имеется утечка: 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 использует этот шаблон для запуска блокирующих операций и он гораздо проще чем аналогичный, использующий worker object.
Я внёс исправление в документацию для того, чтобы пользователей больше не отговаривали наследовать QThread.

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

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

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

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

Автор: epicfailguy93

Источник

Поделиться

* - обязательные к заполнению поля