Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Am I forced to use pthread_cond_broadcast (over pthread_cond_signal) in order to guarantee that *my* thread is woken up?

Tags:

c

pthreads

qt

In the context of interfacing some QT GUI thread (a pthread thread) with some C code, I stumbled over the following problem: I launch the QT Gui thread and, before my C thread resuming its path, I need to make sure that all the graphical objects inside the QT Gui thread had been constructed and they are valid QObjects (since the C code will call QObject:connect() on those).

Introduction let aside, the waiting is made through a pthread_cond_wait() + a condition variable + an associated mutex in the C thread:

int isReady=0;
pthread_mutex_lock(&conditionReady_mutex);
while(isReady==0) {
    pthread_cond_wait(&conditionReady_cv, &conditionReady_mutex);
}
pthread_mutex_unlock(&conditionReady_mutex);

On the other hand, the QT Gui thread constructs its graphical objects and then signals it with:

pthread_mutex_lock(&conditionReady_mutex);
isReady=1;
pthread_cond_broadcast(&conditionReady_cv);
pthread_mutex_unlock(&conditionReady_mutex);

Basic stuff, as you see. But the question is: in the Qt Gui thread, I've been using the pthread_cond_broadcast(), in order to make sure that my C thread is woken up, for sure. Yes, in my current application, I only have a C thread and a Qt Gui thread, and pthread_cond_signal() should do the job of waking up the C thread (since it is guaranteed to wake up at least one thread, and the C thread is the only one).

But, in a more general context, let's say I have three C threads, but I want one (or two) of them to be woken up. A (two) specific thread(s). How do I ensure that?

If I use pthread_cond_signal(), that could simply wake up only the third thread, which would completely miss the point, since the one thread of interest for me is not woken up. OTOH, waking up all the threads, even those which are unneeded, through pthread_cond_broadcast(), that would be overkill.

There is a way to tell pthread_cond_signal() which thread to wake up?

Or, should I introduce more condition variables in order to have a finer granularity over the groups of threads that are woken up with pthread_cond_broadcast()?

Thank you.

like image 395
user1284631 Avatar asked Jun 03 '12 08:06

user1284631


2 Answers

Yes, if you require a specific thread to be woken up then you either need to broadcast the wakeup, or use a separate condition variable for that thread.

like image 145
caf Avatar answered Nov 15 '22 15:11

caf


Unsafe Manners

Calling any generic QWidget method directly from a different thread is unsafe, because the behavior of Qt and underlying C++-generated code in undefined. It may launch a nuclear first strike. You have been warned. Thus you cannot call QWidget::update(), QLabel::setText(...), or any other such methods from a separate thread unless you use safe interthread communication offered by Qt.

Wrong

// in a separate thread
widget->update();
widget->setText("foo");

Right

// in a separate thread
// using QMetaObject::invokeMethod(widget, "update") is unoptimal
QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest),
                            Qt::LowEventPriority);
QMetaObject::invokeMethod(widget, "setText", Q_ARG(QString, "foo"));

On Interthread Communication

A trivial way of communicating with a thread that contains some QObjects involves posting events to it via the static QCoreApplication::postEvent() method. The GUI thread is a common example of a thread where a whole bunch of QObjects reside. Using postEvent() is always thread safe. This method does not care at all which thread it's called from.

There are various static methods scattered across Qt that internally use postEvent(). Their key signature is that they, too, will be static! The QMetaObject::invokeMethod(...) family is such an example.

When you connect signals to slots in QObjects that live in different threads, a queued connection is used. With such a connection, every time the signal is emitted, a QMetaCallEvent is constructed and posted to the target QObject, using -- you guessed it right -- QCoreApplication::postEvent().

So, connecting a signal from a QObject in some non-GUI thread to the QWidget::update() slot is safe, because internally such a connection uses postEvent() to propagate the signals across the thread barrier. The event loop in the GUI thread will pick it up and execute an actual call to the QWidget::update() on your label. Fully thread safe.

Optimizations

The only problem with using a signal-slot connection or method invocation across threads is that Qt is unaware that your updates should be compressed. Every time you emit your signal connected to QWidget::update() in a separate thread, or every time you call invokeMethod, there is an event posted to an event queue.

The idiomatic, if undocumented, way of doing an across-threads update is:

QApplication::postEvent(widget,
                        new QEvent(QEvent::UpdateRequest),
                        Qt::LowEventPriority);

The UpdateRequest events get compressed by Qt. At any time, there can be only one such event in the event queue for a particular widget. So, once you post first such an event, the subsequent ones will be ignored until the widget actually consumes the event and repaints (updates) itself.

So, how about our various widget-setting calls, such as, for example QLabel::setText()? Surely it would be cool if we could somehow compress those as well? Yes, it is certainly possible, but we have to limit ourselves to public Qt interfaces. Qt doesn't offer anything obvious here.

The key is to remove any pending QMetaCallEvent from the widget's event queue before posting another one. This is safe only if we're sure that the queued signal-slot connections to given widget are only coming from us. This is usually a safe assumption, because everything in the GUI thread uses automatic connections, and those will default to directly invoking the slots without posting events to the event queue of the GUI thread.

Here's how:

QCoreApplication::removePostedEvents(label, QEvent::MetaCall);
QMetaObject::invokeMethod(label, "setText", Q_ARG(QString, "foo"));

Note that for performance reasons we use a normalized representation of the slot: no extra spaces, and all const Type & are converted to simply Type.

Note: In my own projects, I sometimes use private Qt event compression APIs, but I won't advocate doing that here.

Postscriptum

The C code should not be calling QObject::connect, I think it's a sign of bad design. If you want to communicate from the C code to the Qt code, use the static QCoreApplication::postEvent(...) (suitably wrapped/exposed to C, of course). To communicate the other way, you can hand-code an event queue in your thread(s). At a minimum, the "event queue" will be just a fixed structure, but the point is to use a mutex to protect access to it.

Another sign of bad design is that code external to a GUI class depends on the individual UI items within that class itself. You should provide a set of signals and slots on the overall UI class (say your MainWindow), and forward those to relevant UI elements internally. You can connect() signals to signals, but not slots to slots -- you'll have to code the forwarding slots manually.

like image 40
Kuba hasn't forgotten Monica Avatar answered Nov 15 '22 16:11

Kuba hasn't forgotten Monica