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

后端 未结 2 877
误落风尘
误落风尘 2021-01-04 21:55

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 thre

相关标签:
2条回答
  • 2021-01-04 22:32

    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.

    0 讨论(0)
  • 2021-01-04 22:39

    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.

    0 讨论(0)
提交回复
热议问题