PDA

View Full Version : The relationship between QT UI and custom thread using 'pthread' in QT Application.



jslim
3rd April 2019, 09:53
Hi all,

I am going to ask about one strange issue.
I hope you understand that my English skills are poor.

I have a embedded(arm, linux) device and there is running a QT(4.7) Application.

QT Application consist of "many Widgets" (QPushButtons, QLabels, etc...) and "a custom thread" (pthread).
The role of the "custom thread" is to communicate with other devices.
The role of the "many Widgets" is to setText the data from the "custom thread" by pressing some button('A' Button) and display the fixed text by pressing another button('B' Button).

The issue is that the data received in the "custom thread" is not updated(call with "setText"), or the program is killed ("Segment fault").

Looking at the pseudocode will make it easier to understand.



QPushButton * buttonA;
QPushButton * buttonB;
QLabel * label;

pthread_mutex_t threadMutex;
pthread_cond_t threadCond;
pthread_t thread;

int recvBuffer;

static void * network_loop( void * parg )
{
int socketFd = -1;
socketFd = socket( "Network Socket open" );

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("Server IP address");
addr.sin_port = htons(Server_Listen_Port);

connect( socketFd, (struct socketaddr*)&addr, sizeof(addr) );

while( 1 ) {
pthread_mutex_lock( &threadMutex );
while( isThreadRunning == true ) {
send( socketFd, "request data", strLength, MSG_NOSIGNAL );
recv( socketFd, (void *)&recvBuffer, BUFFER_SIZE, 0 );
test_widget* mTestWidget = (test_widget*)parg;
mTestWidget->update_text( );
}
pthread_mutex_unlock( &threadMutex );

pthread_mutex_lock( &threadMutex );
if( isThreadRunning == false )
pthread_cond_wait( &threadCond, &threadMutex );
pthread_mutex_unlock( &threadMutex );
}
}

void test_widget::update_text( )
{
label->setText( QString::number(recvBuffer) );
}

test_widget::test_widget( QWidget * parent ) : QWidget(parent)
{
isThreadRunning = false;

pthread_mutex_init( &threadMutex, NULL );
pthread_cond_init( &threadCond, NULL );
pthread_create( &thread, NULL, network_loop, (void *)this );

buttonA = new QPushButton( parent );
buttonB = new QPushButton( parent );
label = new QLabel( parent );

buttonA->setGeometry( 0, 0, 10, 10 );
buttonB->setGeometry( 20, 0, 10, 10 );
label->setGeometry( 0, 40, 20, 10 );
label->setText( "default" );

connect( buttonA, SIGNAL(clicked()), this, SLOT(buttonA_click()) );
connect( buttonB, SIGNAL(clicked()), this, SLOT(buttonB_click()) );
}

void test_widget::buttonA_click( )
{
isThreadRunning = true;
pthread_cond_signal( &threadCond );
}

void test_widget::buttonB_click( )
{
isThreadRunning = false;
label->setText( "good" );
}


This codes tells three things.
1. buttonA push : thread executes. And label->setText calls with received data.
2. buttonB push : thread stops. And label->setText calls with "good"
3. if thread executes, it calls the member function of "test_widget".

There is no problem when starting QT Application. But when I press button 'A' and 'B' alternately, when the button 'A' is pressed at some point, the number is not updated. Then sometimes the program is killed.(Segment fault)
(The above code is a pseudocode, so it may not work. If you want the code for testing, I can provide it.)

I want to know the cause of this issue. Please help me.
Thanks.

anda_skoa
3rd April 2019, 13:46
Rule number 1 when working with threads in Qt: never ever access a widget in a secondary thread.

You can
1) Send an event to the main thread
2) Call a widget slot via QMetaObject::invokeMethod
3) Emit a signal and use a cross-thread signal slot connection.

Any specific reason why you are using low-level, system specific, threading API and not simply QThread?

Cheers,
_

jslim
5th April 2019, 01:46
Thank you very much your reply !! :-)

"Rule number 1 when working with threads in Qt: never ever access a widget in a secondary thread."
==> I'm just curious, is there a reason for that??


"1) Send an event to the main thread
2) Call a widget slot via QMetaObject::invokeMethod
3) Emit a signal and use a cross-thread signal slot connection."
==> very good. However, I think I should study QMetaObject. In fact, I do not understand the QMetaObject documentation. (https://doc.qt.io/archives/qt-4.8/qmetaobject.html#invokeMethod) :-(


"Any specific reason why you are using low-level, system specific, threading API and not simply QThread?"
==> I could use QThread. But, can not use QThread proficiently and there are API functions provided by the embedded device.


Thank you Anda_skoa.

anda_skoa
5th April 2019, 08:44
"Rule number 1 when working with threads in Qt: never ever access a widget in a secondary thread."
==> I'm just curious, is there a reason for that??

Most UI is handled by a single thread, so adding multithread capability, e.g. locking in every widget method, would create lots of overhead for the standard use case.

And Qt makes it very easy to transfer data from a secondary thread to the main thread, so even the MT case is not complicated for the application developer either.



"1) Send an event to the main thread
2) Call a widget slot via QMetaObject::invokeMethod
3) Emit a signal and use a cross-thread signal slot connection."
==> very good. However, I think I should study QMetaObject. In fact, I do not understand the QMetaObject documentation. (https://doc.qt.io/archives/qt-4.8/qmetaobject.html#invokeMethod) :-(


It basically works like this



// Calling a slot without arguments
QMetaObject::invokeMethod(myWidgetPointer, "nameOfSlot", Qt::QueuedConnection);

// Calling a slot with arguments
QMetaObject::invokeMethod(myWidgetPointer, "nameOfSlot", Qt::QueuedConnection, Q_ARG(QString, "someString"), Q_ARG(int, 42));

The QueuedConnection ensures that the slot is called by the thread running the event processing for myWidgetPointer, i.e. the main thread.



"Any specific reason why you are using low-level, system specific, threading API and not simply QThread?"
==> I could use QThread. But, can not use QThread proficiently and there are API functions provided by the embedded device.


The Qt API is far easier to use and in this case has the nice side effect that you can simply emit a signal from within the thread's loop



class MyThread : public QThread
{
Q_OBJECT
protected:
void run() override;

signals:
void updateWidgets();
};

void MyThread::run()
{
int socketFd = -1;
socketFd = socket( "Network Socket open" );

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("Server IP address");
addr.sin_port = htons(Server_Listen_Port);

connect( socketFd, (struct socketaddr*)&addr, sizeof(addr) );

while( 1 ) {
pthread_mutex_lock( &threadMutex );
while( isThreadRunning == true ) {
send( socketFd, "request data", strLength, MSG_NOSIGNAL );
recv( socketFd, (void *)&recvBuffer, BUFFER_SIZE, 0 );

emit updateWidgets();
}
pthread_mutex_unlock( &threadMutex );

pthread_mutex_lock( &threadMutex );
if( isThreadRunning == false )
pthread_cond_wait( &threadCond, &threadMutex );
pthread_mutex_unlock( &threadMutex );
}
}


Obviously you wouldn't even need a thread for this kind of workload because the Qt socket API can do non-blocking/asynchronous data transfer all by itself :)

Cheers,
_