QThreads: Are You Using Them Wrong?

42
QThread Are you doing it wrong? Roland Krause Integrated Computer Solutions based on material from David Johnson

Transcript of QThreads: Are You Using Them Wrong?

QThreadAre you doing it wrong?

Roland KrauseIntegrated Computer Solutions

based on material fromDavid Johnson

What are we talking about!

● Controversy and confusion● Patterns of threading● How QThread works● Pitfalls to avoid● Examples

Controversy

“You are doing it wrong!” - Brad Hughes 2010

“You were not doing so wrong” - Olivier Goffart 2013

“Have you guys figured it out yet?” - Developer 2014

What is this all about?

● A casual reading of some blog posts leads one to imagine there is a “right” way and a “wrong” way to use QThreadhttps://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/http://woboq.com/blog/qthread-you-were-not-doing-so-wrong.htmlhttps://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/https://fabienpn.wordpress.com/2013/05/01/qt-thread-simple-and-stable-with-sources/http://stackoverflow.com/questions/4093159/what-is-the-correct-way-to-implement-a-qthread-example-please

● There are two valid patterns of QThread use● Both are suitable for different use cases

The Reason for the Confusion

● Only one pattern of QThread use prior to 4.4○ Must subclass QThread and implement run()

● New pattern of QThread use since 4.4○ Default implementation for run()○ Default implementation simply calls exec()

● Documentation was not fully updated until Qt 5● Problems arise when mixing the two patterns

Four Threading Patterns

● Runnables - Pass runnable object to thread○ QThreadPool, Java

● Callables - Pass callback or functor to thread○ Qt::Concurrent, std::thread, Boost::thread

● Subclassing - Subclass and implement run()○ QThread, Java

● Event Driven - Events handled in associated thread○ QThread

Notice that QThread has two patterns of use

Four Threading Patterns

Let’s look at these in the context of Qt

Runnable Pattern

QThreadPool / QRunnable ● Create a QRunnable subclass ● Pass runnable object to QThreadPool::start() ● Very simple, but very low level ● Good for “fire and forget” style tasks ● Good for CPU-bound tasks

Example Runnable

class HelloWorldTask : public QRunnable { void run() { qDebug() << "Hello world from thread" << QThread::currentThread(); } } HelloWorldTask *hello = new HelloWorldTask(); // QThreadPool takes ownership // and deletes 'hello' automatically QThreadPool::globalInstance()->start(hello);

Use it for: Asynchronous Tasks with Trivial Parallelism, i.e. no communication

Callable Pattern

QtConcurrent ● Functional programming style APIs for parallel

list processing○ Concurrent Map-Reduce○ Operates concurrently over collections and ranges ○ Supports several STL-compatible container and iterator

types○ Works best with Qt containers that have random-

access iterators, such as QList or QVector.○ Can use: Function pointers, functors, std::bind

● Actual threads are managed by QThreadPool ● Good for parallel tasks

Example: Image processing

Callable Pattern

● QFuture represents the result of an asynchronous computation.

● QFutureWatcher allows monitoring a QFuture using signals-and-slots.

Use it for: Problems with Trivial Parallelism, e.g. indexing of large data sets

Subclassing Pattern

● Subclass QThread ● Re-implement the run() [protected] method

○ default run() implementation calls exec()

● Call start(), start() calls run()● Intended for blocking tasks that don’t need

interaction with the main thread● If you do not really need an event loop in the

thread, you could subclass

Example: Encoding data

Event Driven Pattern

● Create a Worker [Q]Object ○ Implement methods that do the (long running, blocking) work, e.g.

doWork()

● Move this worker object to the thread managed by the QThread object○ Creates thread affinity○ Connect QThread::started to Worker::doWork

● Call QThread::start()○ emits started() when the actual thread has actually started○ Worker's slots will be executed in its thread

● Use this for: Threads that need to interact with other threads through events and/or signals and slots

The Confusion Sets In

There are two patterns of QThread use.

Mixing subclassing and event driven patterns is an easy pitfall to stumble into

Although the Qt documentation for QThread never mentions the QObject::moveToThread() method until the “event pattern” was introduced in Qt-4.8

Where the Serious Trouble Starts

Is when doing things like this:

MyThread::MyThread() : QThread()

{

moveToThread(this);

start();

}

void MyThread::run()

{

connect(Manager, &Manager::newData,

this, &MyThread::processData);

// ...

}

QThread and QObjects

● Each thread can have its own event loop● A QObject lives in the thread in which it is created

○ Events to that object are dispatched by that thread's event loop.

● QObject::moveToThread() changes thread affinity ○ For object and its children ○ The object cannot be moved if it has a parent!

● Unsafe to call delete on a QObject from a different thread ○ As is accessing the object in other ways. Protect with mutexes, etc.. ○ Use QObject::deleteLater()

A QThread is not a Thread - What?

Yes you heard that right: ● QThread is not a Thread, QThread manages a Thread

○ Threads are modes of code execution, they are not objects. ○ This is true for all common frameworks (Posix, Win32, etc.)

● With few exceptions, QThread methods do not run in the Thread of execution.

“All of the functions in QThread were written and intended to be called from the creating thread, not the thread that QThread starts” - Bradley T. Hughes

The Basics of QThread

QThread manages one thread of execution ● The Thread itself is defined by run()

○ Note that run() is a protected method

● Calling start() will create and start the thread ● QThread inherits from QObject

○ Supports properties, events, signals and slots

● Several threading primitive classes enable thread execution control○ QMutex, QSemaphore, QWaitCondition, etc..

QThread Event Loop

Default implementation of run() void QThread::run() {

(void) exec(); }

● The only* method that runs in the thread context

● Starts an event loop running in the thread ● The event loop is key to thread communication

*Qt-5.5 introduces loopLevel()

QThreads and Events

● Can use custom QEvents to communicate between QObjects in different threads

● Event loop of the receiver must be running ● Event loop of the sender is optional ● Bad form to use shared data in the event

object void MainWindow::onActivity() { //... ActivityEvent *event = new ActivityEvent(a, b); QApplication:postEvent(worker, event); }

Signal Slot Connections and Threads

● Qt::DirectConnection ○ Slots are called directly ○ Synchronous behavior

● Qt::QueuedConnection ○ Signals are serialized to an event ○ Asynchronous behavior

● Qt::AutoConnection (default) ○ If both objects have same thread affinity: Direct ○ If objects have different thread affinity: Queued

● Blocking Queued Connection ● Unique Connection

Cross Thread Signals and Slots

● Default connection between objects of different thread affinity is Qt::QueuedConnection

● Sender's signal is serialized into an event ● Event is posted to the receiver's event queue ● Event is deserialized and the slot is executed ● Communication between threads is easy!

This is why QThread self-affinity is just wrong. It implies you want to send cross-thread signals to yourself.

Cross Thread Signals and Slots

● Cross thread signals are really events ○ The receiver needs a running event loop ○ The sender does NOT need an event loop ○ Signals are placed in the event queue

● All threads can emit signals regardless of pattern ○ Only threads with running event loops should have in-

thread slots

Back to Threading Patterns

Let's look closer at the two threading patterns for QThread...

The Subclassing Pattern

Use when task... ● ... doesn't need an event loop ● ... doesn't have slots to be called ● … can be defined within run()

Most classic threading problems Standalone independent tasks

Subclassing Example

class WorkProducer : public QThread{public: WorkProducer(int items=0);protected: virtual void run(); WorkItem produceWork(); ...};

class WorkConsumer : public QThread{public: WorkConsumer(int id);protected: virtual void run(); void consumeWork(const WorkItem &item); ...};

Subclassing Example, cont.

void WorkProducer::run(){ for (int n = 0; n < numitems; n++) { WorkItem item = produceWork();

QMutexLocker locker(&queuelock);

workqueue.enqueue(item); if (worknumwaiting > 0) { workcondition.wakeOne(); worknumwaiting--; } }}

Subclassing Example, cont.

void WorkConsumer::run(){ while (!workisdone) { QMutexLocker locker(&queuelock);

if (workqueue.isEmpty()) { worknumwaiting++; workcondition.wait(locker.mutex()); } else { WorkItem item = workqueue.dequeue(); locker.unlock();

consumeWork(item);

// send an inter thread signal to the GUI thread emit newValue(QString("Thread %1 Value is %2") .arg(threadid) .arg(item.getValue())); } }}

Subclassing Example, cont.

WorkQueue::~WorkQueue(){ // tell all the consumer threads to exit queuelock.lock(); workisdone = true; workcondition.wakeAll(); queuelock.unlock();

// wait for each thread to complete producer->wait(); producer->deleteLater(); foreach (WorkConsumer *consumer, consumerlist) { consumer->wait(); consumer->deleteLater(); }}

Pitfalls of Subclassing

● Providing slots for the subclass ○ Since thread affinity has not changed, and there is no

event loop, slots will be executed in the caller's thread.

● Calling moveToThread(this) ○ Frequently used ''solution'' to the pitfall above ○ Threads must never have affinity with themselves

The Event Driven Pattern

Use when task... ● … is naturally event driven ● … needs to receive communications ● … does not have a single entry point

Inter-dependent tasks “Manager” tasks

Event Driven Example

class Worker : public QObject{ Q_OBJECTpublic: Worker(QObject *parent = 0);

public slots: void process(const QString &filename);signals: void status(int); ...};

Event Driven Example, cont.

void Worker::process(const QString &data){ QFile file(filename); if (!file.open(QIODevice::ReadOnly) { emit status(PROCESS_ERROR); return; } int percent = 0; while (!file.atEnd()) { QByteArray line = file.readLine(); // process line ... emit status(percent); }}

Event Driven Example, cont.

void Window::onStartClicked(){ QThread *thread = new QThread(this); mWorker = new Worker(); mWorker->moveToThread(thread); connect(thread, &QThread::finished, mWorker, &Worker::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(this, &Window::quit, thread, &QThread::quit); connect(this, &Window::newData, mWorker, &Worker::process); connect(mWorker, &Worker::status, this, &Window::processStatus); thread->start();}

Event Driven Example, cont.

void Window::closeEvent(QCloseEvent*){ ... if (mWorker) { QThread *thread = mWorker->thread(); if (thread->isRunning()) { thread->quit(); thread->wait(250); } }}

Drawbacks of Event Driven Pattern

● Does your task really need to be event driven? ● Event starvation

○ Compute intensive tasks take too long to return to event loop

○ Checkout: QThread::isInterruptionRequested()● Fake event loop anti-pattern

○ An entry point never exits to the event loop void Worker::start(){ while (true) { ... }}

Event Driven Plus Subclassing

● Choice of pattern is not an either/or decision

MyThread::run(){ ... initialize objects ... setup workers ... handshaking protocols exec(); ... cleanup thread resources ... wait on child threads}

What About QThreadPool?

QThreadPool is ideal for managing “fire and forget” tasks!

However... ● Hard to communicate with the runnables ● Requires either threading primitives... ● Or requires multiple inheritance from QObject

What about QtConcurrent?

Higher level threading API However... ● Mostly limited to parallel computations ● Recommend to have knowledge of parallel

algorithms ● Recommended to have knowledge of

advanced C++ features

QThread!

Remains the workhorse of Qt threading

● General purpose threading solution ● Solid, Tried and True, Cross-platform

Threading API ● Compare to Boost - Ugh!● QObject wholesome goodness

Now You Are Doing it Right!

There's no one right way to use QThread But there are two good ways to use QThread

Subclassing Pattern Event Driven Pattern

There once was a coder from LodzWho thought that his wife yelled too much

He moved to his own QThreadAnd everything that she said

He ignored - Signal-Slot mismatch!

Thanks David!