Qt:当多个排队的信号调用同一插槽时,如何避免死锁

时间:2019-01-16 02:16:40

标签: qt qthread qtconcurrent

在以下代码中,我在someOperation中遇到了死锁:

class A : public QObject {
    Q_OBJECT
public:
    explicit A(QObject* parent) : QObject(parent), data(0) {}
public slots:
    void slot1() {
        someOperation();
    }
    void slot2() {
        someOperation();
    }
    void slot3() {
        someOperation();
    }
private:
    void someOperation() {
        QMutexLocker lk(&mutex);
        data++;
        QMessageBox::warning(NULL, "warning", "warning");
        data--;
        assert(data == 0);
    }
    int data;
    QMutex mutex; //protect data
};

class Worker: public QThread {
    Q_OBJECT
public:
    explicit Worker(QObject* parent) : QThread(parent) {}
protected:
    virtual void run() {
        // some complicated data processing
        emit signal1();
        // other complicated data processing
        emit signal2();
        // much complicated data processing
        emit signal3();
        qDebug() << "end run";
    }
signals:
    void signal1();
    void signal2();
    void signal3();

};

int main(int argc, char *argv[])
{
        QApplication app(argc, argv);
        A* a = new A(&app);
        Worker* w = new Worker(a);
        QObject::connect(w, SIGNAL(signal1()), a, SLOT(slot1()), Qt::QueuedConnection);
        QObject::connect(w, SIGNAL(signal2()), a, SLOT(slot2()), Qt::QueuedConnection);
        QObject::connect(w, SIGNAL(signal3()), a, SLOT(slot3()), Qt::QueuedConnection);
        w->start();

        return app.exec();
}

有一个线程将发出三个信号,所有这些信号都排队连接到类A的实例,并且所有类A'的插槽都将调用someOperation,并且someOperation受互斥量保护。它将弹出一个消息框。

  

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

在主线程中,当slot1的消息框仍处于模式状态时,似乎调用了slot2,但是那时slot1的锁为mutex,因此为死锁。

如何更改代码以避免死锁?

更新:(2019年1月17日)

我想要存档的是:在slot1完成之前不执行slot2。

应保留的是:

  1. worker是处理数据的后台线程,耗时长;因此,无论如何,这三个信号将从其他线程发出。
  2. 工人不应该被信号挡住。
  3. 插槽应在主线程中执行,因为它们将更新GUI。
  4. someOperation不是可重入的。

2 个答案:

答案 0 :(得分:0)

那是因为您的函数void someOperation()不是reentrant

QMessageBox的静态函数跨越它们自己的事件循环,该事件循环反复调用QCoreApplication::processEvents()

  1. someOperation()的第一次调用的执行被卡在QMessageBox::warning(...)上。
  2. 在这里exec()呼叫processEvents(),3.看到第二个信号
  3. 然后再次调用someOperation()
  4. 尝试重新锁定mutex的位置失败。

如何解决此问题取决于您要实现的目标...


关于您对QThread的一般做法:You're doing it wrong.
(该链接为该主题提供了良好的开端,但不是完整的解决方案。)

您创建并启动后台线程。但是该线程只会发出三个信号,然后结束。

插槽将在主(GUI)事件循环内调用,因为这就是A *a的{​​{3}}。

要使插槽在后台执行,您需要:

  1. 创建没有父项的A实例:A *a = new A();
  2. 使用应用程序作为父项创建您的Worker实例:Worker *w = new Worker(&app);(或者什么也没有,至少没有a
  3. 更改您的A实例的线程关联性:a->moveToThread(Worker);
  4. 不要重写Worker::run(),或者如果您确实要重写(请参见第5点),请调用基本实现:QThread::run();
  5. 从main发出信号(您可以从run()发出信号,但这不是必需的。)

答案 1 :(得分:0)

"someOperation is not reentrant"是一个奇数的要求。如果尝试重新进入该怎么办?鉴于只能从someOperation线程中调用main,所以我只能看到两个选项...

  1. 按照您的尝试,使用互斥锁/屏障等完全阻止。
  2. 基于递归级别计数器进行阻止,并旋转事件循环,直到该计数器递减为零为止。

1)将完全阻塞线程的事件循环,从而阻止当前消息对话框正常运行。

2)将允许所有消息对话框同时进行,而不是序列化它们。

我认为您不必确保someOperation不可重入,而要确保使用方式不会导致重入。

一个选择可能是在自己的QObject上使用一个单独的QThread派生类实例。请考虑以下...

class signal_serialiser: public QObject {
  Q_OBJECT;
signals:
  void signal1();
  void signal2();
  void signal3();
};

如果signal_serialiser的实例移到其自己的线程,如果使用了合适的连接类型,则它可以充当队列来缓冲和转发各种信号。您目前在代码中拥有...

QObject::connect(w, SIGNAL(signal1()), a, SLOT(slot1()), Qt::QueuedConnection);
QObject::connect(w, SIGNAL(signal2()), a, SLOT(slot2()), Qt::QueuedConnection);
QObject::connect(w, SIGNAL(signal3()), a, SLOT(slot3()), Qt::QueuedConnection);

将其更改为...

signal_serialiser signal_serialiser;
QObject::connect(w, SIGNAL(signal1()), &signal_serialiser, SIGNAL(signal1()));
QObject::connect(w, SIGNAL(signal2()), &signal_serialiser, SIGNAL(signal2()));
QObject::connect(w, SIGNAL(signal3()), &signal_serialiser, SIGNAL(signal3()));

/*
 * Note the use of Qt::BlockingQueuedConnection for the
 * signal_serialiser --> A connections.
 */
QObject::connect(&signal_serialiser, SIGNAL(signal1()), a, SLOT(slot1()), Qt::BlockingQueuedConnection);
QObject::connect(&signal_serialiser, SIGNAL(signal2()), a, SLOT(slot2()), Qt::BlockingQueuedConnection);
QObject::connect(&signal_serialiser, SIGNAL(signal3()), a, SLOT(slot3()), Qt::BlockingQueuedConnection);
QThread signal_serialiser_thread;
signal_serialiser.moveToThread(&signal_serialiser_thread);
signal_serialiser_thread.start();

我只做过基本测试,但似乎可以提供所需的行为。