我是否被迫使用pthread_cond_broadcast(通过pthread_cond_signal)以保证* my * thread被唤醒?

在一些QT GUI线程(一个pthread线程)与一些C代码连接的上下文中,我偶然发现了以下问题:我启动了QT Gui线程,在我的C线程恢复其路径之前,我需要确保所有的QT Gui线程中的图形对象已经被构造出来并且它们是有效的QObjects(因为C代码将在那些上调用QObject:connect() )。

引言放弃,等待是通过pthread_cond_wait() +条件变量+ C线程中的关联互斥量进行的:

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

另一方面,QT Gui线程构造其图形对象,然后用以下信号发出信号:

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

基本的东西,如你所见。 但问题是:在Qt Gui线程中,我一直在使用pthread_cond_broadcast() ,以确保我的C线程被唤醒,当然。 是的,在我当前的应用程序中,我只有一个C线程和一个Qt Gui线程,并且pthread_cond_signal()应该完成唤醒C线程的工作(因为它保证唤醒至少一个线程,并且C线程是唯一的一个)。

但是,在更一般的上下文中,假设我有三个C线程,但我希望它们中的一个(或两个)被唤醒。 一个(两个)特定线程。 我该如何确保?

如果我使用pthread_cond_signal() ,那么只能唤醒第三个线程,这将完全错过这一点,因为我感兴趣的一个线程没有被唤醒。 OTOH,通过pthread_cond_broadcast()唤醒所有线程,甚至是那些不需要的线程,这些都是过度的。

有一种方法可以告诉pthread_cond_signal()唤醒哪个线程?

或者,我是否应该引入更多的条件变量,以便在使用pthread_cond_broadcast()唤醒的线程组中获得更精细的粒度?

谢谢。

是的,如果您需要唤醒特定线程,则需要广播唤醒,或者为该线程使用单独的条件变量。

不安全的举止

直接从不同的线程调用任何通用QWidget方法是不安全的,因为Qt和底层C ++的行为 – 生成的代码未定义。 它可能会发动核爆击。 你被警告了。 因此,除非使用Qt提供的安全的线程间通信,否则不能从单独的线程调用QWidget::update()QLabel::setText(...)或任何其他此类方法。

错误

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

 // 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")); 

论网络传播

与包含一些QObject的线程进行通信的一种简单方法是通过静态QCoreApplication::postEvent()方法QCoreApplication::postEvent()发布事件。 GUI线程是一大堆QObject所在的线程的常见示例。 使用postEvent()始终是线程安全的。 这个方法根本不关心哪个线程调用它。

Qt内部有各种静态方法,内部使用postEvent() 。 他们的关键签名是,他们也将是静态的! QMetaObject::invokeMethod(...)系列就是这样一个例子。

将信号连接到生活在不同线程中的QObject中的插槽时,将使用排队连接。 有了这样的连接,每次发出信号时, QMetaCallEvent构造一个QMetaCallEvent并将其发布到目标QObject,使用 – 你猜对了 – QCoreApplication::postEvent()

因此,将来自某个非GUI线程中的QObject的信号连接到QWidget::update()插槽是安全的,因为在内部这样的连接使用postEvent()来跨越线程障碍传播信号。 GUI线程中的事件循环将拾取它并执行对标签上的QWidget::update()的实际调用。 完全线程安全。

优化

在线程中使用信号槽连接或方法调用的唯一问题是Qt不知道您的更新应该被压缩。 每次在单独的线程中发出连接到QWidget::update()信号,或者每次调用invokeMethod ,都会有一个事件发布到事件队列。

进行跨线程更新的惯用方法(如果没有记录)是:

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

UpdateRequest事件由Qt压缩。 在任何时候,特定小部件的事件队列中只能有一个这样的事件。 因此,一旦您首先发布此类事件,后续的事件将被忽略,直到窗口小部件实际消耗该事件并重新绘制(更新)本身。

那么,我们的各种小部件设置调用如何,例如QLabel::setText() ? 当然,如果我们能以某种方式压缩这些也会很酷吗? 是的,这肯定是可能的,但我们必须限制自己使用公共Qt接口。 Qt在这里没有提供任何明显的东西。

关键是在发布另一个QMetaCallEvent之前从窗口小部件的事件队列中删除任何挂起的QMetaCallEvent 。 只有当我们确定给定小部件的排队信号槽连接仅来自我们时,这才是安全的。 这通常是一个安全的假设,因为GUI线程中的所有内容都使用自动连接,并且这些默认情况下将直接调用插槽而不将事件发布到GUI线程的事件队列。

这是如何做:

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

请注意,出于性能原因,我们使用了槽的规范化表示:没有多余的空格,所有的const Type &都被转换为简单的Type

注意:在我自己的项目中,我有时使用私有Qt事件压缩API,但我不会在此提倡这样做。

Postscriptum

C代码不应该调用QObject::connect ,我认为这是设计糟糕的标志。 如果你想从C代码到Qt代码进行通信,请使用静态QCoreApplication::postEvent(...) (当然,适当地包装/暴露给C)。 要以另一种方式进行通信,您可以手动编写线程中的事件队列。 “事件队列”至少只是一个固定的结构,但重点是使用互斥锁来保护对它的访问。

设计错误的另一个迹象是GUI类外部的代码依赖于该类本身的各个UI项。 您应该在整个UI类(比如您的MainWindow )上提供一组信号和插槽,并在内部将它们转发到相关的UI元素。 您可以connect()信号connect()到信号,但不能将插槽连接到插槽 – 您必须手动编码转发插槽。