PDA

View Full Version : GUI with worker thread, signal/slot communication elaboration



Mr_Cloud
6th May 2014, 08:57
Hello All,

I made a data acquisition software with the worker/DAQ code in one thread and the visualization/GUI in the other. My approach is using this (https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/) method. I'm trying to communicate between these threads using signal/slot connections by emitting a void dataUpdate(unsigned char *buf); from the worker thread and visualizing the data in the GUI thread.

The problem is that I'm not using any mutex whatsoever and I'm quite certain that the same memory gets accessed by both the threads at the same time, thus displaying bogus poo in my graph every few frames.

I want to implement a QMutex or QSemaphore of some sort, although I'm a bit apprehensive about threads waiting for one another. Under no circumstance do I want the worker (acquisition) thread to be waiting to have write access to the variable, because if the DAQ thread starts waiting for GUI, I start losing data.

My approach is different to the mandelbrot example due to the fact that the data processing gets done in the GUI, while the DAQ thread focuses solely on acquisition. Example:




class myDAQ : public QObject{ //this is the one that should have no dead time
public:
myDAQ(){
/*constructor stuff here*/
buff=new unsigned char[2048];
}

public slots:
void acquireData(){
//get the data;
emit sendToBeGraphed(buff);
}

signals:
void sendToBeGraphed(unsigned char *buf);// usually a few thousand bytes worth of data

private:
unsigned char *buff;
};

class myGUI : public QMainWindow{
public:
myGUI(){
thread=new QThread;
acquisitionClass=new myDAQ;

connect(acquisitionClass,SIGNAL(sendToBeGraphed(un signed char*)),this,SLOT(visProc(unsigned char*)));

acquisitionClass->moveToThread(thread);
thread->start();
}

public slots:
void visProc(unsigned char *raw){
decodeData();
plotData();
}

private:
QThread *thread;
myDAQ *acquisitionClass;

}



How do I ensure that when visProc() gets called, the buff variable is not being modified? I was thinking to mutex.lock() inside the myDAQ::acquireData(), but would I have to mutex.lock() in the GUI class as well?

Thanks


Regards,
Mr_Cloud

anda_skoa
6th May 2014, 11:02
The easiest way is of course to not use the same buffer.
E.g. copy the data before emitting it.

Cheers,
_

Mr_Cloud
7th May 2014, 06:03
I get what you mean, but does the data have to be dynamically allocated for that to work? 'Cause then there's no point. (Only a pointer, har har har derp derp)

I've had trouble communicating between threads if I didn't emit pointers to data, or single values. So for example declaring unsigned int buff[2048]; emit sendToBeGraphed(buff); would not be processed by visProc() in the other thread/class. Instead, only unsigned char *buff=new unsigned char[2048]; would.

I've also tried emitting QStrings but I was never successful and the program would usually crash. Am I missing something trivial? Thanks

Lesiok
7th May 2014, 08:49
Use QByteArray instead of unsigned char[] or QString.

anda_skoa
7th May 2014, 12:05
I get what you mean, but does the data have to be dynamically allocated for that to work? 'Cause then there's no point.

You already do allocate the array dynamically


buff=new unsigned char[2048];

As Lesiok said, QByteArray is usually a nicer way of doing that, especially if you want to copy



QByteArray copyOfBuff = buff;
copyOfBuff.detach(); // create deep copy
emit sendToBeGraphed(copyOfBuff);


Cheers,
_

Mr_Cloud
16th May 2014, 02:57
Hey guys,

Thank you for your response. I can confirm that QByteArray is a working means of communication between threads (as expected). I wonder however why I haven't had any luck with QVector...

Anyway it appears that now I'll have to deal with multiple types of data to transfer between threads: double, char, string/QString etc. and I have found this (http://blog.mrroa.com/post/25044784315/tip-trick-how-to-split-a-string-using-c) resource to split strings using delimiter characters. The code is



vector<string> split(string str, string delim)
{
unsigned start = 0;
unsigned end;
vector<string> v;

while( (end = str.find(delim, start)) != string::npos )
{
v.push_back(str.substr(start, end-start));
start = end + delim.length();
}
v.push_back(str.substr(start));
return v;
}


Previously, I created a commWrapper class which just contained all the different types of variables I need to transfer between threads and I would emit newData(commWrapper*); and connect it to the recipient thread, but that seemed to crash or unexpectedly exit a lot.



class commWrapper
{
public:
double a,b,c;
string derp,hurr;
};

//usage
commWrapper *hai=new commWrapper;
hai->a=69;
hai->derp="lol this is a terrible example";

emit newData(hai);


It is worth to note that emitting a non-pointer version of commWrapper - aka, emitting newData(commWrapper); instead of newData(commWrapper*); - would not work at all and thread communication was non-existent.

Now I'm thinking that I am going to emit a QByteArray with comma-delimited data and just process it in the recipient thread with the split() function above. Hopefully that will eradicate crashes. Thoughts?

anda_skoa
16th May 2014, 10:21
I think the correct way in this case would be to use a non-pointer to commWrapper.
In order to make that work across threads, you'll have to declare commWrapper as a Qt meta type.
http://qt-project.org/doc/qt-4.8/qmetatype.html

Basically you add a macro to the header that declares commWrapper and call qRegisterMetaType<commWrapper>() in main() and you are good.

Cheers,
_

Mr_Cloud
18th May 2014, 17:48
Awesome, that seems to have done it! Thank you, anda_skoa. Funnily enough, the comma-delimited QByteArray approach was in no way stable by the time I implemented your solution above.

I've qRegister'dMetaType the commWrapper before instantiating my objects or threads and tested heavily. No crashes thus far. Winrar.7z

Oh programming, why art thou such a heartless butch sometimes..

..Feel free to leisurely interchange "programming" with "Mr_Cloud's Qt/C++ knowledge" on the previous line lol

Edit: Should I add the registerMetaType in the main.cpp before return app.exec()? I currently have it in my constructor for the main class with all the UI stuff. Does it matter where it gets declared?

anda_skoa
19th May 2014, 08:08
You don't need to put it into main(), the constructor of your main class is OK as well.

It just needs to be called before the cross-thread connection is created (well, actually used).

Cheers,
_

Mr_Cloud
19th May 2014, 08:20
Yep awesome. Thanks!