Qt QTcpSocket: Как предотвратить мертвую блокировку в сигнале readyRead? - PullRequest
3 голосов
/ 27 января 2012

Мне нужна некоторая помощь в Qt в Windows 7. Похоже, что сигнал Qt readyRead() испускается асинхронным вызовом процедуры, который приводит к тому, что код выполняется одновременно, но в том же потоке .

В моем примере у меня есть очередь, к которой должен обращаться DoRead(), а в DoTimer(), к которой обращается блокировка.Вся операция выполняется в пользовательском (основном) потоке .Однако иногда, когда DoRead() называется, возникла мертвая блокировка.Код останавливает выполнение в DoRead().Мертвая блокировка может быть воспроизведена, если отображается окно сообщения и выполнение DoTimer() останавливается.Однако я был удивлен, увидев, что OnRead() все еще вызывается одновременно.Единственное объяснение для меня заключается в том, что OnRead() вызывается APC Windows.

См. Статью MSDN Асинхронные вызовы процедур :

Асинхронный вызов процедур(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();
    }
};

Edit 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);
}

Блокировки не требуются, если естьнет одновременного доступа к очереди (нет многопоточности).

1 Ответ

2 голосов
/ 28 января 2012

Ваша единственная проблема - звонок на

QMessageBox::critical(...);

Этот вызов блокируется, пока вы не нажмете кнопку. Но поскольку вы вызывали его, все еще удерживая блокировку, ваши DoRead блокируются.

Нет абсолютно никаких причин открывать окно сообщений, удерживая этот замок!

Если вы все еще хотите, чтобы ваш DoTimer отвечал во время отображения окна сообщения, не используйте статические вспомогательные методы, такие как QMessagebox :: критический.

Лучше сделай это

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

Но, тем не менее, из вашего кода я все равно не вижу смысла использовать мьютексы. Там нет параллелизма, верно?

...