[Qt-interest] Understanding thread affinity -- some questions about a lengthy example

K. Frank kfrank29.c at gmail.com
Mon May 17 04:47:57 CEST 2010


Hello All -

I am trying understand thread affinity in Qt.  After
thinking about various questions I could ask, I decided
instead to present an example, and say what I think is
happening, and then ask for feedback about what I am
getting wrong.

There are a lot of assumptions, both explicit and tacit,
about thread affinities in this example.  I think if I
were to ask questions, I would only ask about the
assumptions I realize I am making.  By using an example,
I hope to get feedback on the assumptions I don't realize
I am making, as well.

Here is a sketch of some example code.  It's not meant
to be complete, or necessarily be able to run, and I
haven't tested it.

The basic idea is to create some threads and objects with
various thread affinities, and then call some signals and
slots.


// *** the semi-pseudo sample code:

TestObject : public QObject {
   Q_OBJECT
   public slots:
      void slot();
};

TestThread : public QThread {
   public:
      TestThread (bool block, TestObject *obj_a, TestObject *obj_b) :
         block_(block), obj_a_(obj_a), obj_b_(obj_b) {
         moveToThread (this);
      }
      void run() {
         obj_c_ = new TestObject;
         connect (this, SIGNAL(sig_a()), obj_a_, SLOT(slot()));
         connect (this, SIGNAL(sig_b()), obj_b_, SLOT(slot()));
         connect (this, SIGNAL(sig_c()), obj_c_, SLOT(slot()));
         emit sig_a();
         emit sig_b();
         emit sig_c();
         if (block_) {  // if block_ is true, never call exec()
            while (true)  sleep(1);
         }
         exec();
      }
      void emit_a() { emit sig_a(); }
      void emit_b() { emit sig_b(); }
      void emit_c() { emit sig_c(); }
   public signals:
      void sig_a();
      void sig_b();
      void sig_c();
   private:
      bool block_;
      TestObject *obj_a_, *obj_b_, *obj_c_;
};

// some code in the main gui thread...

TestObject *o1 = new TestObject;
TestObject *o2 = new TestObject;

TestThread *t1 = new TestThread (false, o1, o2);
TestThread *t2 = new TestThread (true,  o1, o2);

o2->moveToThread (t1);

t1->start();
t2->start();

t1->emit_a();
t1->emit_b();
t1->emit_c();

t2->emit_a();
t2->emit_b();
t2->emit_c();

// and we assume that the main gui thread has
// its event loop (exec()) running properly

// *** end of example code:

Now the comments and questions:

I will be careful with terminology, and make a point of
distinguishing between a QThread object and a thread of
execution.

After executing

   TestThread *t1 = new TestThread;
   TestThread *t2 = new TestThread;

there are (presumably at least) three QThread objects:
one for the main gui thread, and t1 and t2.  At this
point there is only one thread of execution, that of
the main gui thread.  Because t1->start() and t2-start()
have not yet been called, there are not yet threads
of execution associated t1 and t2.  (At least things
act as if there is only one thread of execution.  Qt
and the OS could have already created threads of execution
for t1 and t2, but left them in a quiescent state.)
Is this right?

After executing

   t1->start()
   t2->start()

there are four instances of TestObject: o1, o2, t1->obj_c_,
and t2->obj_c_.

As I understand thread affinity, an instance of a QObject
has affinity for a single specific instance of a QThread.

In our example, the affinities are:

   o1:  main gui QThread
           because it was created by the main gui thread of
           execution, i.e., the thread of execution associated
           with the main gui QThread

   o2:  t1
           because of  o2->moveToThread (t1);

   t1->obj_c_:  t1
           because it was created in the thread of execution
           associated with t1.  this is because t1->obj_c_
           was created in t1's run() method, and run() was
           called normally, that is run() was called automatically
           by t1's start() method that created the the thread
           of execution associated with t1.

   t2->obj_c_:  t2
           same analysis as for t1->obj_c_

Anyway, that's my story.  But I'm not necessarily sticking to
it, if somebody tells me I've got it wrong...

We also have two instances of TestThread, t1 and t2.  These also
have thread affinities:

   t1:  t1
           specifically because of the call  moveToThread (this);
           in the constructor of TestThread.  (I claim it is legal
           to call this in the constructor, because moveToThread
           is a method of the base class QObject, which is fully
           constructed at this point.)  If we had not called
           moveToThread, the thread affinity of t1 would have
           been to the main gui QThread.

   t2:  t2
           same analysis as for t1

Now, my understanding is that the sole (or at least in practice,
the primary) purpose of thread affinities is to determine
which thread of execution is used to call a slot when a signal
is emitted.  That is, it is to determine whether a Direct Connection
or a Queued Connection is established when connect() is called.

My theory is as follows:

1) The thread of execution that calls connect() is irrelevant to
which type of connection is established.

2) The thread affinities of the emitter and receiver at the time
of the call to connect() determines whether a Direct or Queued
Connection is made.

3) The thread of execution that calls "emit signal" does not
affect the type of connection -- and this has potentially
tricky consequences.

I do wonder whether this theory is correct, and I would
appreciate any feedback.

According to my theory, we get the following connection
types:

For t1 (called in the run() method):

   connect (this, SIGNAL(sig_a()), obj_a_, SLOT(slot()));
      queued:  t1 (this) and o1 (obj_a_) have different affinities

   connect (this, SIGNAL(sig_b()), obj_b_, SLOT(slot()));
      direct:  t1 and o2 (obj_b_) both have t1 affinity

   connect (this, SIGNAL(sig_c()), obj_c_, SLOT(slot()));
      direct:  t1 and t1->obj_c_ both have t1 affinity

For t2 (called in the run() method):

   connect (this, SIGNAL(sig_a()), obj_a_, SLOT(slot()));
      queued:  t2 (this) and o1 (obj_a_) have different affinities

   connect (this, SIGNAL(sig_b()), obj_b_, SLOT(slot()));
      queued:  t2 and o2 (obj_b_) have different affinities

   connect (this, SIGNAL(sig_c()), obj_c_, SLOT(slot()));
      direct:  t2 and t2->obj_c_ both have t2 affinity

Now for the signals:

In t1's run() method:

   emit sig_a();
      o1->slot() is called asynchronously through a queued
      connection by the main gui thread of execution.

   emit sig_b();
      o2->slot() is called synchronously though a direct
      connection by t1's thread of execution.

   emit sig_c();
      t1->obj_c_->slot() is called synchronously though a direct
      connection by t1's thread of execution.

In t2's run() method:

   emit sig_a();
      o1->slot() is called asynchronously through a queued
      connection by the main gui thread of execution.  This
      happens even though t2's event loop (exec()) never
      executes, because you only need the event loop on
      the receiving end of a queued connection.

   emit sig_b();
      o2->slot() is called asynchronously though a queued
      connection by t1's thread of execution.  This also
      succeeds without t2's event loop.

   emit sig_c();
      t2->obj_c_o->slot() is called synchronously though a direct
      connection by t1's thread of execution.  Because this is
      a direct connection it doesn't go through t2's event loop.
      Therefore, this call succeeds, even though t2's event loop
      never executes.

Note: If  we had moved o1 to t2:

   o1->moveToThread (t2);

then emit sig_a() in t1's run() would not succeed in calling o1->slot().
I believe that emit sig_a() would, through the queued connection,
post a "call o1->slot()" event to t2's event loop, but, because
t2's event loop never executes, this event would never be processed,
and o1->slot() would not be called.  (Is this right?)

Now we try to "cheat" the threading model, by calling signals from
an "unexpected" thread of execution.

   t1->emit_a();
      t1's "emit sig_a()" is called by the main gui thread of
      execution.  This causes o1->slot() to be called asynchronously
      through a queued connection by the main gui thread of execution.
      The reasoning is that queued vs. direct is determined by the
      thread affinities of the emitter and receiver QObjects at the
      time connect is called, and not by the thread of execution
      that actually calls "emit sig_a()".

   t1->emit_b();
      t1's "emit sig_b()" is called by the main gui thread of
      execution.  This causes o2->slot() to be called synchronously
      through a direct connection by the main gui thread of
      execution.  Because o2->slot() doesn't actually do anything,
      it is thread-safe, so this should work, but in a more realistic
      case we would risk race conditions because o2->slot() can also be
      called (and normally would be called) by t1's thread of execution.

   t1->emit_c();
      same analysis as for t1->emit_b().  t1->obj_c_->slot() is
      called synchronously by the main gui thread of execution,
      and the same concerns are raised.

   t2->emit_a();
      same analysis as for t1->emit_a().  o1->slot() is called
      asynchronously by the main gui thread of execution.

   t2->emit_b();
      t2's "emit sig_b()" is called by the main gui thread of
      execution.  This causes o2->slot() to be called asynchronously
      through a queued connection by t1's thread of execution.

   t2->emit_c();
      same analysis as for t1->emit_b() and t1->emit_c().
      t2->obj_c_->slot() is called synchronously by the main gui
      thread of execution.  Because the synchronous call bypasses
      the never-executed event loop of t2, t2->obj_c_->slot()
      does get called.


I hope that I have made the example clear enough that
you can start picking it apart.  Any insight or clarification
on this threading stuff would be greatly appreciated.

Thanks.


K. Frank



More information about the Qt-interest-old mailing list