Qt QTcpSocket:如何防止readyRead信号死锁?

发布于 2024-12-29 12:26:24 字数 2381 浏览 2 评论 0原文

我在 Windows 7 上的 Qt 中需要一些帮助。似乎 Qt readyRead() 信号是由异步过程调用发出的,这会导致代码并发执行,但在相同 线程。

在我的示例中,我有一个队列,应该由 DoRead() 访问,并且在 DoTimer() 中由锁访问。整个操作在 ui(主)线程中运行。然而有时当DoRead()被调用时会发生死锁。代码在 DoRead() 中停止执行。如果显示消息框并且 DoTimer() 的执行停止,则死锁是可重现的。然而,我惊讶地发现 OnRead() 仍然被并发调用。对我来说唯一的解释是,OnRead() 是由 Windows APC 调用的。

请参阅 MSDN 文章 异步过程调用< /a>:

异步过程调用 (APC) 是在特定线程上下文中异步执行的函数。当 APC 排队到线程中时,系统会发出软件中断。 下次调度线程时,它将运行APC函数

我关于 readyRead() 可能是 APC 的假设是真的吗?

无论哪种情况,我可以采取什么措施来防止死锁?我需要访问 DoRead() 中的队列来填充队列,并访问 DoTimer() 中的队列(当然还有其他方法)来读取、写入或删除同一队列中的条目。递归互斥体不是解决方案,因为两个调用都发生在同一线程中。

class QMySocket : public QTcpSocket {
public:
    QMySocket() {
        ...
        connect(this, SIGNAL(readyRead()), this, SLOT(DoRead()));
        connect(_MyTimer, SIGNAL(timeout()), this, SLOT(DoTimer()));
        ...
    }
private:
    QTimer* _MyTimer;
    QQueue<int> _MyQueue;
    QMutex _Lock;

    void DoRead() {
        _Lock.lock(); // <-- Dead Lock here (same Thread ID as in DoTimer)
        _MyQueue... // Do some queue operation
        // DoSomething
        _Lock.unlock();
    }

    void DoTimer() {
        _Lock.lock();
        QQueue<int>::iterator i = _MyQueue.begin();
        while (i != _MyQueue.end()) { // Begin queue operation
            if (Condition) {
                QMessageBox::critical(...);
                i = _MyQueue.erase(i);
            } else {
                i++;
            }
        } // end queue operation
        _Lock.unlock();
    }
};

编辑 2:据我所知,这与 APC 无关。问题只是 QMessageBox 创建的额外消息循环。

所有消息都将排队并在任何队列操作后显示,而不是直接调用 QMessageBox。

void DoTimer() {
    QList<QString> Messages;
    QQueue<int>::iterator i = _MyQueue.begin();
    while (i != _MyQueue.end()) { // Begin queue operation
        if (Condition) {
            Messages.append(...);
            i = _MyQueue.erase(i);
        } else {
            i++;
        }
    } // end queue operation
    QMessageBox::critical(Messages);
}

如果没有对队列的并发访问(无多线程),则不需要锁。

I need some assistance in Qt on Windows 7. It seems that Qt readyRead() signal is emited by an asynchronus procedure call which causes the code to be executed concurrent but in the same thread.

In my example I have a queue which should be accessed by DoRead() and in DoTimer() which is accessed by a lock. Entire operation is running in ui (main) thread. However sometimes as DoRead() is called a dead lock occurred. The code stops execution in DoRead(). The dead lock is reproduceable if the Message Box is showed and so execution of DoTimer() is halted. However I was surprised to see that OnRead() is still called in concurrent. The only explanation for me is, that OnRead() is called by an Windows APC.

See MSDN article Asynchronus Procedure Calls:

An asynchronous procedure call (APC) is a function that executes asynchronously in the context of a particular thread. When an APC is queued to a thread, the system issues a software interrupt. The next time the thread is scheduled, it will run the APC function.

Am I true with my assumption that readyRead() could be an APC?

In either case, what could I do to prevent dead locks? I need to access the queue in DoRead() to fill the queue and in DoTimer() (and other methods of course) to read, write or delete entries from same queue. Recursive mutexes are no solution since both calls occurs in same thread.

class QMySocket : public QTcpSocket {
public:
    QMySocket() {
        ...
        connect(this, SIGNAL(readyRead()), this, SLOT(DoRead()));
        connect(_MyTimer, SIGNAL(timeout()), this, SLOT(DoTimer()));
        ...
    }
private:
    QTimer* _MyTimer;
    QQueue<int> _MyQueue;
    QMutex _Lock;

    void DoRead() {
        _Lock.lock(); // <-- Dead Lock here (same Thread ID as in DoTimer)
        _MyQueue... // Do some queue operation
        // DoSomething
        _Lock.unlock();
    }

    void DoTimer() {
        _Lock.lock();
        QQueue<int>::iterator i = _MyQueue.begin();
        while (i != _MyQueue.end()) { // Begin queue operation
            if (Condition) {
                QMessageBox::critical(...);
                i = _MyQueue.erase(i);
            } else {
                i++;
            }
        } // end queue operation
        _Lock.unlock();
    }
};

Edit 2: This had nothing to do with APC as I found out. The problem was only the extra message loop created by QMessageBox.

Instead calling QMessageBox directly, all messages will be queued and showed after any queue operation.

void DoTimer() {
    QList<QString> Messages;
    QQueue<int>::iterator i = _MyQueue.begin();
    while (i != _MyQueue.end()) { // Begin queue operation
        if (Condition) {
            Messages.append(...);
            i = _MyQueue.erase(i);
        } else {
            i++;
        }
    } // end queue operation
    QMessageBox::critical(Messages);
}

Locks are not required if there is no concurrent access to the queue (no multithreading).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

万水千山粽是情ミ 2025-01-05 12:26:24

您唯一的问题是对“此调用”的调用

QMessageBox::critical(...);

会阻塞,直到您按下按钮。但由于您在仍持有锁的情况下调用它,因此您的 DoRead 死锁。

绝对没有理由在持有该锁的情况下打开消息框!

如果您仍然希望 DoTimer 在显示消息框时做出响应,请不要使用 QMessagebox::ritic 等静态便捷方法。

最好这样做

   // Somewhere in the constructor ...
   QMessageBox* msgBox = new QMessageBox( this );
   msgBox->setAttribute( QWidget::WA_DeleteOnClose );
   msgBox->setStandardButtons( QMessageBox::Ok );
   msgBox->setWindowTitle( tr("Error") );
   msgBox->setModal( true );
   //...

void DoTimer() {
    _Lock.lock();
    // DoSomething
    _MyQueue... // Iterate over queue, and do some queue operation (delete entires for exmaple)
    _Lock.unlock();
    msgBox->setText( tr("DingDong!") );
    if (!msgBox->isVisible())
        msgBox->open( this, SLOT(msgBoxClosed(QAbstractButton*)) );
}

void MyWidget::msgBoxClosed(QAbstractButton*) {
   qDebug("Byebye msgbox");
}

但是,从您的代码中我仍然看不到任何使用互斥体的理由。没有并发,对吧?

Your only problem is the call to

QMessageBox::critical(...);

This call blocks until you press a button. But since you called it while still holding the lock, your DoRead deadlocks.

There is absolutely no reason to open a messagebox while holding that lock!

If you still want your DoTimer to respond while showing the messagebox don't use the static convenience methods like QMessagebox::critical.

Better do this

   // Somewhere in the constructor ...
   QMessageBox* msgBox = new QMessageBox( this );
   msgBox->setAttribute( QWidget::WA_DeleteOnClose );
   msgBox->setStandardButtons( QMessageBox::Ok );
   msgBox->setWindowTitle( tr("Error") );
   msgBox->setModal( true );
   //...

void DoTimer() {
    _Lock.lock();
    // DoSomething
    _MyQueue... // Iterate over queue, and do some queue operation (delete entires for exmaple)
    _Lock.unlock();
    msgBox->setText( tr("DingDong!") );
    if (!msgBox->isVisible())
        msgBox->open( this, SLOT(msgBoxClosed(QAbstractButton*)) );
}

void MyWidget::msgBoxClosed(QAbstractButton*) {
   qDebug("Byebye msgbox");
}

But still, from your code I don't see any reason to use mutexes anyway. There is no concurrency, right?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文