[Qt-interest] Question about the thread affinity of a QThread object

K. Frank kfrank29.c at gmail.com
Fri May 21 18:19:57 CEST 2010


Sean -

Thanks.

On Fri, May 21, 2010 at 4:53 AM, Sean Harmer
<sean.harmer at maps-technology.com> wrote:
> ...
> On Thursday 20 May 2010 23:29:36 K. Frank wrote:
>> ...
>> Now, I _think_ I have seen warnings, maybe in the documentation, or maybe
>> in a list discussion, that one should not emit signals from a QThread's
>> run() method.
>
> Emitting signals from a QThread object (even in its run() function) is fine. I
> think the warnings you are referring to were about not putting *slots* in the
> QThread object and instead having worker objects created in run() to do the
> work.

Okay, I think I understand the concern.  Let me see if i can concoct an
example that illustrates this:

   class Receiver : public QThread {
   Q_OBJECT
      public:
         Receiver() : i_(0), j_(0) {}
         void run() {
            j_ = i_ + 1;  // manipulate the private data
            exec();
         }
      public slots:
         void receive() { i_ = j_+1;)  // let an outside signal
manipulate the data
      private:
         int i_, j_;  // some dummy data to manipulate
   };

   class Sender : public QObject {
   Q_OBJECT
      public:
         void emit_send() { emit send(); }  // not really necessary?
      public signals:
         void send();
   };

   // the main gui thread, call it t0

   s = new Sender;   // thread affinity t0, but irrelevant
   t1 = new Receiver;   // thread affinity t0 (not t1)
   connect (s, SIGNAL(send()), t1, SLOT(receiver()));
   t1->start();
   s->emit_send();  // danger of race condition manipulating t1's data

   // ...

I would like to check my understanding:

The connect call creates a Qt::AutoConnection between s and t1.
Therefore how t1->receive() is called (synchronous call or posted
event) depends on the thread of execution that emits the signal
and the thread affinity of the receiver.

In this case, the signal, s->send() is emitted by thread t0, which
is also the thread for with the receiver, t1, has affinity.  Therefore
the slot, t1->receive(), is called synchronously by thread t0,
essentially as if "s->emit-signal();" had been replaced by
"t1->receive();".

The race condition occurs because two different threads, t0 and t1,
are monkeying around with t1's private data:  t0 through the
signal / slot connection, and t1 through the line of code
"j_ = i_ + 1;" in t1's run() method.

(The call to exec() in t1->run() is, in a sense, a red herring.  The
direct connection bypasses exec(), and t1->receive() would be
called, even if exec() had been left out.)

Do I have this example (and its analysis) right, and does it
illustrate the potential pitfall with having QThread's implement
slots?

> It's not that you can't do this, just that it is clearer in which thread
> context the slots will get executed. Remember that the QThread object itself
> has affinity with its parent object which lives in the thread which created
> the QThread object in question - unless you explicitly move it of course.

So, can one "fix" the example -- eliminate the race condition -- by moving
t1 to itself?

   t1 = new Receiver;   // thread affinity t0 (not t1)
   t1->moveToThread (t1);   // now t1 has thread affinity t1

Is it legal (and moral) to do this in Receiver's constructor?

   Receiver::Receiver() : i_(0), j_(0) {
      moveToThread (this);
   }

I _think_ that this would be officially legitimate from a C++
perspective.  Is it consistent with Qt's inner workings?

> To avoid confusion when it comes to maintenance time 2 years down the line and
> the new employee fails to spot the importance of your moveToThread() call and
> removes it then bad things will happen. If you explicitly create worker
> objects in QThread::run() it becomes much clearer to see that the slots of
> these objects will be executed in the context of your QThread object and not
> the thread your QThread object has affinity to.

Agreed.  From a readability and maintainability standpoint (and probably
also from the perspective of separation of concerns), it's better to just
let QThread control a thread of execution, and use additional worker
objects to implement the processing logic.

>> ...
>
> The connection type to use (queued/direct etc) is determined when you call
> emit (unless you force it to something else in your connect call). The signal
> is being emitted from within t2's context and is connected to t1 which has
> affinity with t1 and so a queued connection is used.

I would like to verify my understanding of this;  (I think Thiago said
essentially
the same thing earlier.)

When connect is called with Qt::AutoConnection (the default), the type of
connection is determined not when connect is called, but rather, on a
call-by-call basis when emit signal is called.

So, in my example:

   s = new Sender;   // thread affinity t0, but irrelevant
   t1 = new Receiver;   // thread affinity t0 (not t1)
   connect (s, SIGNAL(send()), t1, SLOT(receiver()));  // defaults to
AutoConnection
   t1->start();

   s->emit_send();  // direction-connection; thread t0 calls t1->receive()
   t1->moveToThread (t1);   // now t1 has thread affinity t1
   s->emit_send();  // queued-connection; thread t1 calls t1->receive()

Is this right?

>> ...
> ...
>> If t2's thread affinity is irrelevant to how the signal is propagated, and
>> therefore the signal / slot connection is (properly) a queued connection,
>> what are all the warnings about?
>
> I think you imagined them wrt emitting signals from a thread. I do not think
> emitting signals form a thread is a problem. The Mandelbrot example does this
> so even the trolls do it :-)

Yes, I think that clears things up.  I remembered correctly that there were
warnings, but it hadn't sunk in exactly what those warnings were about.

Thanks for your explanations.

K. Frank

> ATB,
>
> Sean



More information about the Qt-interest-old mailing list