[Qt-interest] Question about the thread affinity of a QThread object
Sean Harmer
sean.harmer at maps-technology.com
Fri May 21 20:25:07 CEST 2010
Hi,
On 21/05/2010 17:19, K. Frank wrote:
>> 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?
Yes I think your analysis is correct. The Qt docs explicitly mention
protecting access to the thread objects member with a QMutex or similar
if you add slots directly to the thread object.
>> 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?
Yes it is perfectly legal from a C++ and Qt point of view. Although as
stated it is not necessarily good practise as it muddies the waters
about the context in which slots on that object will be called.
>> 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?
Yes.
> 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.
No problem. Good luck as multi-threading is challenging and leads to
some interesting debugging challenges of non-deterministic behaviour if
you miss something! It is rewarding when it works though as it's a shame
to see all those CPU cores going to waste ;-)
Have a good weekend,
Sean
More information about the Qt-interest-old
mailing list