[Development] Evolving Qt's multithreading API

Sze Howe Koh szehowe.koh at gmail.com
Mon Feb 25 13:36:24 CET 2013


On 24 February 2013 01:24, Thiago Macieira <thiago.macieira at intel.com> wrote:
> On sábado, 23 de fevereiro de 2013 20.26.11, Sze Howe Koh wrote:
>> On 23 February 2013 00:11, Thiago Macieira <thiago.macieira at intel.com>
> wrote:
>> > The fact is that any QObject that is returned from those functions -- if
>> > any -- must belong to the calling thread. That implies the necessary
>> > guarantees in terms of signal emissions.
>> >
>> > For example, if the functions return a QObject pointer, a signal-signal
>> > connection from the actual target object's finished() signal to the
>> > returned object's finished() will apply the necessary queueing semantics.
>> >
>> > That also speaks against returning a QThread*.
>>
>> I haven't understood your point, sorry. Can you please clarify what
>> "necessary guarantees" you were referring to, and how this "speaks
>> against returning a QThread*"?
>
> Sure.
>
> This is an un-optimised approach. There are a few ways to make it more
> efficient, but that's for later.
>
> Suppose we have:
>         Result *QThread::start(<task functor or object>)
>
> Internally, it will use an internal class derived from QThread that runs the
> task in its run() function. When the thread finishes, QThread finished(). Now,
> as you noticed, unless we do something, the thread could start and finish
> before the user has a chance to connect the finished() signal from the returned
> object.
>
> What I am suggesting is a trampoline object: in the setup phase, a this
> trampoline QObject is created and it contains just one signal: finished(). That
> signal is connected to the Result object's finished(). When the task finishes
> and run() is about to return, it emits that signal -- from the auxiliary
> thread. That means the actual thread might have emitted finished(), but the
> Result object didn't -- that can only happen at the next event loop iteration,
> due to the queue semantics.
>
> So you note that the returned object cannot be the QThread that we created. It
> must be something else. Interestingly, since it can't be the QThread, it means
> the trampoline object *can* be the QThread: we just need to connect
> QThread::finished() to QThreadTask::finished().


Thank you for the comprehensive explanation. I know little about Qt's
internal mechanisms, so I'm curious now.

Could the guarantee also be provided by posting QThread::start() into
the event loop during setup?

    QMetaObject::invokeMethod(thread, "start", Qt::QueuedConnection)


If so, what's the cost of having two QObjects (trampoline object and
returned object), and how does it compare to the cost of deferring the
start until the next event loop iteration?

In my mind, the costs of 3 different approaches to starting this new method are:

- Trampoline object: 2 QObjects, delay of 0 loop iterations, 0 extra
LOC for user
- Queued auto-start: 1 QObject, delay of 1 loop iteration, 0  extra LOC for user
- Manual start: 1 QObject, delay of 0 loop iterations, 1 extra LOC for user

Which costs the "least"? How do the optimizations to the trampoline
change things?


> QThread signals the status of the thread, but QFutureWatcher signals the
> status of the future, the task. The thread itself may not have finished.
>
> What I'm saying is that you should not return a QThread, but something else.
> And then you can connect QThread's finished() signal to that something else's
> finished(). As I described above, that alone will be enough to guarantee the
> right semantics.

Then, should we put this new function outside the QThread class? The
task-management interface feels like it abstracts away the underlying
QThread, so it would be messy to mix the API.


Regards,
Sze-Howe



More information about the Development mailing list