[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