c++qthreadqt6

QThread for an indefinite non-looping lifecycle


I understand how QThread is supposed to be used conceptually, with something akin to the following

MyWorker *worker = new MyWorker();
QThread *thread = new QThread();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(doWork()));
connect(worker, SIGNAL(done()), thread, SLOT(quit()));
connect(worker, SIGNAL(done()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();

which will execute the doWork() method within MyWorker within the thread, stopping the thread and performing memory cleanup once worker announces it's done via done(). This is how I've seen all examples, and it really appears geared towards a Worker does an operation in a separate thread type of use case.

Based on what I've understood:

So this leads me to two questions for a potential use case which feels like it goes a little against the prescribe QThread formula, and which I haven't really been able to find discussed anywhere.

Question 1 - Would simply moving the object just allow for slots to execute in the new thread?

By this I mean:

MyWorker *worker = new MyWorker();
QThread *workerThread = new QThread();
worker->moveToThread(workerThread);
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(workerThread, SIGNAL(finished(), thread, SLOT(deleteLater()));
workerThread->start();

In the above, there is no dedicated "execute" slot/method within MyWorker, meaning that it would perform any triggers slots in thread (provided the connection was done using Qt::AutoConnection or Qt::QueuedConnection of course). Thus if elsewhere in the code I were to do:

MyTrigger trigger;
connect(&trigger, SIGNAL(triggered()), worker, SLOT(doSomething()));

then any time trigger would emit the triggered() signal, worker would execute the doSomething() slot within workerThread rather than whatever thread is trigger belongs to? Thus, slot processing would be multi-threaded without there being an explicit doWork() item to be executed in the thread.

Note: I can see the issues with memory management in the example above, but this is just purely an illustration of the idea.

I'm fairly confident that this would be the case, and if so this leads me to my next question...

Question 2 - What thread is an object created in?

Or perhaps a slightly different way to phrase this would be, can I ensure that objects are created in a particular thread? Continuing from the above example, let's say that I want to replace MyWorker with some kind of wider capability, where I want the whole capability to operate in this separate thread. I'm thinking of essentially expanding on the above idea, but rather than using a single class, leveraging a series of classes, all of which are built-out from a central class. What I'm thinking is something along the lines of

class MyCapability: public QObject {
    Q_OBJECT

public:
    MyOtherWorker* getOtherWorker() {
        return worker2;
    }

public slots:
    void setup() {
        worker1 = new MyWorker();
        worker2 = new MyOtherWorker();

        connect(worker1, SIGNAL(something()), worker2, SLOT(other()));
    }

    void doSomething() {
        worker1->doSomething();
    }

private:
    MyWorker *worker1;
    MyOtherWorker *worker2;
};

MyCapability *capability = new MyCapability();
QThread *capabilityThread = new QThread();
capability->moveToThread(capabilityThread);
connect(capabilityThread, SIGNAL(started()), capability, SLOT(setup()));
connect(capabilityThread, SIGNAL(finished()), capability, SLOT(deleteLater()));
connect(capabilityThread, SIGNAL(finished(), thread, SLOT(deleteLater()));
capabilityThread->start();

Thus:

When this is used from external

MyTrigger trigger;
connect(&trigger, SIGNAL(doSomethingTrigger()), capability, SLOT(doSomething()));
connect(&trigger, SIGNAL(doOtherTrigger()), capability->getOtherWorker(), SLOT(other()));

trigger will be connecting via Qt::QueueConnection as capability, worker1, and worker2 all live within the capabilityThread (assuming trigger is in a different one).

What I'm thinking is creating a "pocket" of functionality in this manner that exists in a single thread, separate from the rest of the application, such that the rest of the application can largely ignore what thread it may (or may not) live in, but which will perform its processing/execution in its dedicated thread via signals/slots in a safe and reliable manner.

Note: again, this example code is a bit of a minefield in terms of memory stability/management, but I'm trying to make sure that my understanding of the QThread concepts is correct.

Is my understanding on these two fronts correct? What mistaken assumptions am I working from?

Thanks!


Solution

  • Thus, slot processing would be multi-threaded without there being an explicit doWork() item to be executed in the thread

    Yes, whole point of workers is to not have explicit while (haveWork()) doWork() (it's kinda still there though), just an eventloop that ready to execute tasks as soon as they arrive.

    What thread is an object created in?

    Function creates an object in a thread it runs in (current thread). setup() will be executed in capabilityThread, so worker1 and worker2 will be created and live in capabilityThread.

    Notice that if you want to access data from another thread not by signal-slot mechanism, you need to use syncronisation (QMutex) (like in capability->getOtherWorker()).

    You can validate all your assumptions about what runs where using qDebug() << QThread::currentThreadId();

    such that the rest of the application can largely ignore what thread it may (or may not) live in

    It will only work if you do every data access using signal-slot mechanism (request data, send data). Otherwise you need syncronisation.

    Take a look at Qt Concurrent. It has some nice high level multithreading primitives (QFuture, QPromise, map-reduce, and thread pool).