QT信号和插槽在应用程序中使用单个线程

时间:2016-07-14 14:25:11

标签: multithreading qt mutex signals-slots

我很难理解为例如发生的事情。两个不同的信号连接到两个不同的插槽,当没有完成一个插槽时,发出其他插槽的信号(对于两个连接到各自信号的插槽直接连接),其中应用程序只有“一个”线程。

这是来自QT官方文档:

  

直接连接:发出信号时立即调用插槽。插槽在发射器的线程中执行,该线程不一定是接收器的线程。

     

排队连接:当控制返回到接收者线程的事件循环时,将调用该槽。插槽在接收器的线程中执行。

与排队连接不同,它“直接”表示直接连接。这是否意味着,如果第一个插槽尚未完成时发出第二个信号,第一个插槽将被中断,即使应用程序是单线程应用程序,它也将与第二个插槽同时运行?如果是这样,为什么我没有看到任何警告,应该使用互斥锁来锁定两个插槽都可以访问的变量。

也许我误解了整个“直接”和“排队”连接的事情。

3 个答案:

答案 0 :(得分:2)

我认为你误解的不是排队和直接版本,而是整个信号/插槽机制。当您同步或异步地需要知道应用程序的另一部分(可能是单线程还是多线程)何时完成其工作时,信号/插槽系统是回调问题的更优雅的解决方案。

直接/排队

在深入了解基础知识之前,让我们先解决这个问题。

  • 如果是直接连接,则无论插槽是否在不同的线程上运行,您的代码都始终在单个线程上执行。 (在大多数情况下,这是一个非常糟糕的想法)
  • 如果连接排队且插槽在同一个线程内运行,则它会像直接连接一样完全。如果插槽在另一个线程上运行,则插槽将在其正在运行的正确线程上下文中执行,因此使代码执行成为多线程。
  • 如果连接是自动这是默认设置,那么它会选择合适的连接类型。

现在有一种方法可以强制你的代码执行跳转到另一个线程的一个槽中,即通过调用一个方法:

QMetaObject::invokeMethod( pointerToObject*, "functionName", Qt::QueuedConnection);

基础知识

所以让我们快速浏览信号/插槽机制。系统中有三个参与者:信号,插槽和连接()

信号基本上都是“嘿!我已经完成”的消息。插槽是代码中进行后处理的地方,因此一旦信号完成,您就可以在代码的插槽部分执行某些操作。

connect()是组织在何处以及由于什么原因发生的事情的方式。信号/槽的执行顺序与代码执行顺序相同。先到先得,因为你提到了单线程。在不同的多线程环境中。老实说,执行顺序无关紧要,如果您正在尝试使用信号/插槽并需要保证执行顺序,那么您正在设计错误的应用程序。理想情况下,您使用信号/插槽机制的方式与函数式编程的工作方式相同,您可以将消息传递给下一个实例来处理数据。

足够的咆哮,让我们来看一些实际细节:

signals:
    void a();
    void b();
slots:
    void sa();
    void sb();

案例1

connect( a -> sa );  // Simplified Notation. Connect signal a to slot sa
connect( b -> sb );

:: emit a(); -> sa is executed
:: emit b(); -> sb is executed

案例2

connect ( a -> sa );
connect ( a -> sb );
connect ( b -> sb );

:: emit a(); -> sa & sb are executed
:: emit b(); -> sb is executed

我认为这应该说清楚。如果您有任何疑问,请告诉我

答案 1 :(得分:1)

直接连接意味着发出信号并调用插槽的快捷方式是简单的方法调用,使emit调用直接跳转到插槽中。 排队连接将调用置于队列中,该队列在Qt事件循环再次运行时立即处理,或者通过调用QCoreApplication::processEvents()强制执行,与所有其他事件一致直到那时排队。

在单线程应用程序中(更准确地说:当发送方和接收方对象同时存在于同一个线程中时),除了在调用QObject::connect()时另有说明外,直接连接是默认连接。这意味着在执行插槽或其他代码期间,此代码中的任何emit都会立即调用连接的插槽 - 这可能是您的意图,有时也不是。

你没有说明你的信号存在什么问题,但是要注意永恒的循环,死锁和其他锁定/互斥问题;单线程代码中根本不需要互斥锁。保持信号/插槽调用链尽可能简单,尽可能避免在插槽中使用emit

答案 2 :(得分:1)

直接连接完全就像通过函数(方法)指针调用一样。没有“中断”,除非您认为在下面的代码中printf()“中断”main()

// main.cpp
#include <cstdio>
int main() {
  printf("Hello\n");
}

所有代码都在同一个线程中运行。 main()printf()从不同时运行:当printf()运行时,main()被暂停。 printf()返回后,main()会恢复。直接连接到信号的插槽/仿函数也是如此。

例如,让我们使用以下ObjectMonitor类来查看正在发生的事情。

// https://github.com/KubaO/stackoverflown/tree/master/questions/sigslot-nest-38376840
#include <QtCore>
struct Monitor {
   int & depth() { static int depth = 0; return depth; }
   const char * const msg;
   Monitor(const char * msg) : msg{msg} {
      qDebug().noquote().nospace() << QString(depth()++, ' ') << msg << " entered";
   }
   ~Monitor() {
      qDebug().noquote().nospace() << QString(--depth(), ' ') << msg << " left";
   }
};
struct Object : QObject {
   Q_SIGNAL void signal1();
   Q_SIGNAL void signal2();
   Q_SLOT void slot1() { Monitor mon{__FUNCTION__}; }
   Q_SLOT void slot2() { Monitor mon{__FUNCTION__}; }
   Q_SLOT void slot3() {
      Monitor mon{__FUNCTION__};
      emit signal2();
   }
   Q_OBJECT
};

让我们signal1直接与广告位slot1slot2slot3相关联。此外,让我们signal2直接与广告位slot1slot2相关联:

int main() {
   Monitor mon{__FUNCTION__};
   Object obj;
   QObject::connect(&obj, &Object::signal1, &obj, &Object::slot1);
   QObject::connect(&obj, &Object::signal1, &obj, &Object::slot2);
   QObject::connect(&obj, &Object::signal1, &obj, &Object::slot3);
   QObject::connect(&obj, &Object::signal2, &obj, &Object::slot1);
   QObject::connect(&obj, &Object::signal2, &obj, &Object::slot2);
   emit obj.signal1();
}
#include "main.moc"

输出清楚地表明,任何直接连接到您发出的信号的插槽都会在信号运行时执行。此外,直接连接的插槽/仿函数不需要事件循环。最后,请记住,信号“只是”一个moc生成的方法,它调用所有直接连接的插槽/仿函数。

main entered
 slot1 entered
 slot1 left
 slot2 entered
 slot2 left
 slot3 entered
  slot1 entered
  slot1 left
  slot2 entered
  slot2 left
 slot3 left
main left