PDA

View Full Version : signal/slots across threads in Qt4



Ambiorix
28th July 2006, 10:09
Hello,

Qt4 claims to have a thread safe way of emitting signals between threads. Many of Qt4's slots have Qt4 data types in their signature. The copy constructors of these types are reentrant but not thread safe. However this problem is avoided since most of the time const references are used as slot arguments e.g. void mySlot(QString const& arg). That means that the slot gets the address of the data but cannot modify these data. Okay! But what about the thread that emitted the signal. I see trouble when in the emitting thread the QString goes out of scope or is modified before the slot dealt with the QString. What stunned me was that my program nevertheless crashed when I used in the emitting thread a const QString that did not went out of scope!! When I wrote similar code for a slot with an integer argument the program did not crash. My conclusions: 1. Qt4 is doing something dirty when queing a signal to the receivers event loop. 2. Qt4's signal/slot mechanism across threads is NOT THREAD SAFE.

I include my code which I have build using the Visual Studio .NET 2003 compiler on a Windows XP-system.
Can anybody either confirm my observations/conclusions or state whether I did some wrong coding?
Thanks in advance!


// FILE 1: receiver.h
#ifndef __RECEIVER__H__
#define __RECEIVER__H__
#include <QtCore>



class Receiver : public QObject
{
Q_OBJECT
public:
Receiver(QObject *parent = 0) : QObject(parent)
{
}
public slots:
void mySlot(QString const& paS);
void myIntegerSlot(int paI);
};





#endif


// FILE 2 emitter.h
#ifndef __EMITTER__H__
#define __EMITTER__H__
#include <QtCore>

class Receiver;



class Emitter : public QObject
{
Q_OBJECT
public:
Emitter(QObject *parent = 0) : QObject(parent)
{
}
void emitSignal(QString const& paS);
void emitIntegerSignal(int paI);
signals:
void newString(QString const& paS);
void newInteger(int paI);
};



class EmitterThread : public QThread
{
public:
EmitterThread(Receiver *receiver);
void run();
private:
Receiver *meReceiver;
Emitter *meEmitter;
};


#endif



// FILE 3 receiver.cpp
#include "receiver.h"
#include <iostream>
using namespace std;

void Receiver::mySlot(QString const& paS)
{
cout << paS.toStdString() << endl;
}


void Receiver::myIntegerSlot(int paI)
{
cout << paI << endl;
}



// FILE 4: emitter.cpp
#include "emitter.h"
#include "receiver.h"


#include <iostream>
using namespace std;

void Emitter::emitSignal(QString const& paS)
{
emit newString(paS);
}


void Emitter::emitIntegerSignal(int paI)
{
emit newInteger(paI);
}




EmitterThread::EmitterThread(Receiver *receiver) : QThread(0), meReceiver(receiver)
{
meEmitter = new Emitter;
QObject::connect(meEmitter,SIGNAL(newString(QStrin g const&)),
meReceiver,SLOT(mySlot(QString const&)),
Qt::QueuedConnection);
QObject::connect(meEmitter,SIGNAL(newInteger(int)) ,
meReceiver,SLOT(myIntegerSlot(int)),
Qt::QueuedConnection);

}

/* This run method crashes
void EmitterThread::run()
{
QString loText("");
while (true)
{
QThread::sleep(1);
meEmitter->emitSignal(loText);
loText += QString("_");
}
}
*/


/* This run method crashes
void EmitterThread::run()
{
while (true)
{
this->sleep(1);
meEmitter->emitSignal(QString("I am not thread safe"));
}
}
*/

/* This run method crashes
void EmitterThread::run()
{
while (true)
{
QThread::sleep(1);
{
QString loText("I go out of scope while my colleague thread is using me!");
meEmitter->emitSignal(loText);
}
}
}
*/

// This run method crashes
void EmitterThread::run()
{
QString const loText("I go not out of scope and am not modified");
while (true)
{
this->sleep(1);
meEmitter->emitSignal(loText);
}
}




/* This run method does not crash (it emits a copy of an integer)
void EmitterThread::run()
{
int i = 0;
while (true)
{
this->msleep(10);
meEmitter->emitIntegerSignal(i);
i++;
}
}
*/




// FILE 5: main.cpp
#include "receiver.h"
#include "emitter.h"

#include <QtCore>
#include <QtGui>

int main(int argc, char *argv[])
{
QApplication app(argc,argv);
Receiver loReceiver; // lives in Main Thread

EmitterThread loET(&loReceiver);
loET.start();
return app.exec();
}

jacek
28th July 2006, 10:35
I see trouble when in the emitting thread the QString goes out of scope or is modified before the slot dealt with the QString.
There won't be any trouble, because in case of queued connections Qt makes a copy of all parameters.


Can anybody either confirm my observations/conclusions
I tried all "crashing" versions of run() method and they didn't crash on my system (PLD Linux, Qt 4.1.4, g++ 3.3.6). How often does it crash on your system?

wysota
28th July 2006, 10:37
Hello,
Hi


Qt4 claims to have a thread safe way of emitting signals between threads. Many of Qt4's slots have Qt4 data types in their signature. The copy constructors of these types are reentrant but not thread safe. However this problem is avoided since most of the time const references are used as slot arguments e.g. void mySlot(QString const& arg). That means that the slot gets the address of the data but cannot modify these data. Okay! But what about the thread that emitted the signal. I see trouble when in the emitting thread the QString goes out of scope or is modified before the slot dealt with the QString.
No, that's not true. Queued connections perform a serialisation and deserialisation of objects passed as signal arguments, so it doesn't matter if the object goes out of scope before the slot is executed or not. You can see that if you create some custom object and try to use it as a signal argument or if you try to use a non-const reference to some object. Result:
"QObject::connect: Cannot queue arguments of type 'QString&'"


What stunned me was that my program nevertheless crashed when I used in the emitting thread a const QString that did not went out of scope!! When I wrote similar code for a slot with an integer argument the program did not crash. My conclusions: 1. Qt4 is doing something dirty when queing a signal to the receivers event loop. 2. Qt4's signal/slot mechanism across threads is NOT THREAD SAFE.

My conclusion:
1. Your conclusions seem not to be reliably confirmed, as your test program runs all the time while I am writing this post and it didn't crash. BTW. You had a spelling error in one of the connect statements... Maybe that was the problem?


I include my code which I have build using the Visual Studio .NET 2003 compiler on a Windows XP-system.
I tested it under Linux/GCC4 and it runs fine.

Ok, I am killing the app now and trying some other version of "crashing" run()... Seems to work flawlessly...

jacek
28th July 2006, 10:40
Does this crash?
EmitterThread::EmitterThread(Receiver *receiver) : QThread(0), meReceiver(receiver)
{
// empty
}

void EmitterThread::run()
{
meEmitter = new Emitter;
QObject::connect(meEmitter,SIGNAL(newString(QStrin g const&)),
meReceiver,SLOT(mySlot(QString const&)),
Qt::QueuedConnection);
QObject::connect(meEmitter,SIGNAL(newInteger(int)) ,
meReceiver,SLOT(myIntegerSlot(int)),
Qt::QueuedConnection);
QString loText("");
while (true)
{
QThread::sleep(1);
meEmitter->emitSignal(loText);
loText += QString("_");
}
}

Ambiorix
28th July 2006, 11:13
Hello Jacek & Wysota,

Thank you for running my program and giving feedback about the copying/serialisation of the queued signal arguments.

The code that you posted Jacek works fine and I believe to understand why. In my original code I created the emitter object in the constructor of the thread. So the emitter object was also living in the main thread. Thus I was using queued connections between objects in the same (main) thread. By moving the creation of the emitter object to the run method, the emitter lives now in the thread.

Kind regards,

Ambiorix

jacek
28th July 2006, 12:16
So the emitter object was also living in the main thread. Thus I was using queued connections between objects in the same (main) thread. By moving the creation of the emitter object to the run method, the emitter lives now in the thread.
Yes, many people run into this problem. I wonder why your version was crashing on windows, but not on Linux. I guess it must have something to do with a different way of handling thread data in those systems.

gfunk
28th July 2006, 19:20
I ran the original crashing code in VS2005 WinXP, but it doesn't crash on my system. :p

Ambiorix
31st July 2006, 08:05
Hello Gfunk,

I cannot explain why the code crashes on my system but not on yours. Maybe it is in the compiler flags that I am using or in the Qt version. I include the build output so that you know which flags and versions I am using.

The feedback that I get when the crash occurs is the following:
QThread object destroyed while thread is still running.
QMutex::lock(): Deadlock detected in thread 2660


build:
scons: Reading SConscript files ...
Loading qt4 tool...
scons: done reading SConscript files.
scons: Building targets ...
cl /nologo /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MT /TP -DQT_GUI_LIB /IC:\Qt\4.1.1\include /IC:\Qt\4.1.1\include\QtGui /IC:\Qt\4.1.1\include\QtCore /c emitter.cpp /Foemitter.obj
emitter.cpp
cl /nologo /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MT /TP -DQT_GUI_LIB /IC:\Qt\4.1.1\include /IC:\Qt\4.1.1\include\QtGui /IC:\Qt\4.1.1\include\QtCore /c main.cpp /Fomain.obj
main.cpp
C:\Qt\4.1.1\bin\moc -o moc_emitter.cpp emitter.h
cl /nologo /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MT /TP -DQT_GUI_LIB /IC:\Qt\4.1.1\include /IC:\Qt\4.1.1\include\QtGui /IC:\Qt\4.1.1\include\QtCore /c moc_emitter.cpp /Fomoc_emitter.obj
moc_emitter.cpp
C:\Qt\4.1.1\bin\moc -o moc_receiver.cpp receiver.h
cl /nologo /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MT /TP -DQT_GUI_LIB /IC:\Qt\4.1.1\include /IC:\Qt\4.1.1\include\QtGui /IC:\Qt\4.1.1\include\QtCore /c moc_receiver.cpp /Fomoc_receiver.obj
moc_receiver.cpp
cl /nologo /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /EHsc /MT /TP -DQT_GUI_LIB /IC:\Qt\4.1.1\include /IC:\Qt\4.1.1\include\QtGui /IC:\Qt\4.1.1\include\QtCore /c receiver.cpp /Foreceiver.obj
receiver.cpp
link /nologo /subsystem:console /OUT:safety.exe /LIBPATH:C:\Qt\4.1.1\lib qtgui4.lib qtcore4.lib receiver.obj emitter.obj main.obj moc_receiver.obj moc_emitter.obj
scons: done building targets.