Qt QTcpSocket: Как предотвратить мертвую блокировку в сигнале readyRead?
Мне нужна помощь в Qt на Windows 7. Кажется, что Qt readyRead()
Сигнал генерируется асинхронным вызовом процедуры, который приводит к одновременному выполнению кода, но в том же потоке.
В моем примере у меня есть очередь, к которой должен получить доступ DoRead()
И в DoTimer()
который доступен с помощью замка. Вся операция выполняется в пользовательском (основном) потоке. Однако иногда как DoRead()
называется мертвой блокировкой. Код останавливает выполнение в DoRead()
, Мертвая блокировка может быть воспроизведена, если отображается окно сообщения и, таким образом, выполнение DoTimer()
остановлен Однако я был удивлен, увидев, что OnRead()
по-прежнему вызывается одновременно. Единственное объяснение для меня заключается в том, что OnRead()
вызывается Windows APC.
См. Статью MSDN Асинхронные вызовы процедур:
Асинхронный вызов процедуры (APC) - это функция, которая выполняется асинхронно в контексте определенного потока. Когда APC ставится в очередь в поток, система выдает программное прерывание. В следующий раз, когда поток запланирован, он запустит функцию APC.
Верен ли я с моим предположением, что readyRead()
может быть БТР?
В любом случае, что я мог сделать, чтобы предотвратить мертвые замки? Мне нужно получить доступ к очереди в 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);
}
Блокировки не требуются, если нет параллельного доступа к очереди (нет многопоточности).
1 ответ
Ваша единственная проблема - это звонок
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");
}
Но, тем не менее, из вашего кода я не вижу смысла использовать мьютексы в любом случае. Там нет параллелизма, верно?