Qt中的那些坑(一)---由信号引起的死锁

【写在前面】

        最近在用 QMutex 的时候,偶然出现了死锁。

        这个场景并不常见,并且可能是自己的习惯导致。

        不过既然出现了,那么在以后编程中就需要注意。


【正文开始】

        先直接上代码吧:

#include <QApplication>
#include <QMutex>
#include <QThread>
#include <QWidget>

class Test : public QWidget
{
    Q_OBJECT

public:
    Test(QWidget *parent = nullptr) : QWidget(parent) {
        connect(this, &Test::signalA, this, &Test::testB);
    }

    void test() {
        m_mutex.lock();
        //do something
        emit signalA();
        m_mutex.unlock();
    }

public slots:
    void testB() {
        m_mutex.lock();
        //do something
        //QThread::msleep(1000);
        m_mutex.unlock();
    }

signals:
    void signalA();

private:
    QMutex m_mutex;
};

int main(int argc, char **argv)
{
    QApplication a(argc, argv);

    Test t;
    t.show();
    t.test();

    return a.exec();
}

#include "main.moc"

        这里要提一下,QObject::connect() 的连接类型默认是 Qt::AutoConnection ,在同一线程中即为 Qt::DirectConnection

        因此,在代码里,test() 函数实际为:

void test() {
    m_mutex.lock();
    signalA() call -> testB() {
        m_mutex.lock();
        //do something
        m_mutex.unlock();
    }
    m_mutex.unlock();
}

        可以看到,互斥锁 m_mutex 连续锁定( lock() )了两次,而第二次锁定将导致当前线程被挂起(例子中为主线程),并且,没有任何地方能够解锁( unlock() ),此时也就产生了死锁。

        出现这种情况的原因还是我太习惯用信号了,实际上,如果 testB() 是另外一个线程的函数,那么 signalA() 将是以 Qt::QueueConnection 的方式连接到 testB() ,死锁就不会产生:

#include <QApplication>
#include <QMutex>
#include <QThread>
#include <QDebug>

class Test : public QThread
{
    Q_OBJECT

public:
    Test(QObject *parent = nullptr) : QThread(parent) {
        connect(this, &Test::signalA, this, &Test::testB);
    }

    void test() {
        m_mutex.lock();
        //do something
        emit signalA();
        m_mutex.unlock();
        qDebug() << QThread::currentThreadId() << " test unlock()";
    }

public slots:
    void testB() {
        m_mutex.lock();
        //do something
        m_mutex.unlock();
        qDebug() << QThread::currentThreadId() << " testB unlock()";
    }

signals:
    void signalA();

protected:
    void run() {
        test();
    }

private:
    QMutex m_mutex;
};

int main(int argc, char **argv)
{
    QApplication a(argc, argv);

    Test t;
    t.start();

    return a.exec();
}

#include "main.moc"

        testB() 在主线程,test() call signalA() 则处于 run() 函数也就是子线程中。

        其运行结果如图:

        互斥锁正确的解锁了两次,即消除了死锁,当然,这并不是正确的解决方案 ( 因为利用了 QueueConnection 进入了事件循环( 队列 ),只是表面上是消除了,与多线程无关 )

        实际上,真正的解决方案相当简单:只需将 emit signalA() 移出 { lock() unlock() } 即可

        同样,更需要注意的是:QMutexLocker

        还是最开始的场景,这次使用 QMutexLocker

#include <QApplication>
#include <QWidget>
#include <QMutex>
#include <QThread>

class Test : public QWidget
{
    Q_OBJECT

public:
    Test(QWidget *parent = nullptr) : QWidget(parent) {
        connect(this, &Test::signalA, this, &Test::testB);
    }

    void test() {
        QMutexLocker locker(&m_mutex);
        //do something
        emit signalA();
    }

public slots:
    void testB() {
        QMutexLocker locker(&m_mutex);
        //do something
    }

signals:
    void signalA();

private:
    QMutex m_mutex;
};

int main(int argc, char **argv)
{
    QApplication a(argc, argv);

    Test t;
    t.show();
    t.test();

    return a.exec();
}

#include "main.moc"

        结果仍是产生了死锁。

        当然,这也是 QMutexLocker 这类 Locker 的弊端 (在析构中 unlock),所以要谨慎啊喂(#`O′)。


【结语】

        最后,一点建议:

  • QMutex 的 lock() 和 unlock() 之间尽量只包含对共享变量的读 / 写操作,不要有任何其他函数调用(除非保证不会产生死锁)。
  • QMutexLocker,尽量自己控制作用域(看得见的析构):{     QMutextLocker(&mutex);     },在作用域结束时析构。
  • 一定,一定,一定要谨慎。
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦起丶

您的鼓励和支持是我创作最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值