Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.221.15.15] |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Ситуация супер простая! Все лишь разребание очереди пакетов.
Бывает редко ситуация, когда поток зависает на строке ЗАВИСОН, хотя на самом деле в очереди есть 1 элемент. Подскажите советом. Привожу полный код. // хидер class QOutgoingPacketsThread : public QThread { Q_OBJECT QMutex m_Mutex; QWaitCondition m_Condition; QVector<QNetPacket> m_Packets; public: QOutgoingPacketsThread(QObject *parent); ~QOutgoingPacketsThread(); void EnqueuePacket(const QNetPacket &packet); void run(); void Stop(); signals: void send_packet(QNetPacket packet); private: }; void QOutgoingPacketsThread::EnqueuePacket(const QNetPacket &packet) { m_Mutex.lock(); m_Packets.push_back(packet); //qDebug() << "Push Packet: " << packet; m_Condition.wakeAll(); m_Mutex.unlock(); } void QOutgoingPacketsThread::run() { while (true) { QMutexLocker locker(&m_Mutex); // Деструктор класса QMutexLocker -- отпирает мьютекс m_Condition.wait(&m_Mutex); // ожидание неполнения очереди, ЗАВИСОН while (m_Packets.size()) { QNetPacket p = m_Packets[0]; //qDebug() << "Process Packet: " << p; if (p.empty()) { return; // end work } emit send_packet(p); m_Packets.erase(m_Packets.begin()); } } } void QOutgoingPacketsThread::Stop() { // запускаем пустые данные чтобы показать что надо завершать EnqueuePacket(QNetPacket()); // пустой пакет wait(); } |
Сообщ.
#2
,
|
|
|
если я правильно понимаю, то если в начале вызвать функцию EnqueuePacket(const QNetPacket &packet), а только после этого запустить поток, то будет зависон пока не придет следующий пакет.
Соответственно, если мы стоим где-то тут: void QOutgoingPacketsThread::run() { while (true) { // [B]ТУТ СТОИМ, ЕЩЕ МЬЮТЕКС НЕ ЗАБЛОКИРОВАЛСЯ[/B] QMutexLocker locker(&m_Mutex); // Деструктор класса QMutexLocker -- отпирает мьютекс m_Condition.wait(&m_Mutex); // ожидание неполнения очереди, ЗАВИСОН while (m_Packets.size()) { QNetPacket p = m_Packets[0]; if (p.empty()) { return; // end work } emit send_packet(p); m_Packets.erase(m_Packets.begin()); } } } и была вызвана функция EnqueuePacket, то в очереди так и будет висеть один пакет, пока не придет следующий. В данном случае, до вызова m_Condition.wakeAll(); необходимо гарантировать, что рабочий поток стоит на строчке m_Condition.wait(&m_Mutex). В примерах рассматривается именно такая ситуация. http://doc.qtsoftware.com/4.5/qwaitcondition.html#details |
Сообщ.
#3
,
|
|
|
sploid
мысль понял, но в данном случае, есть 100% гарантия что поток уже запущен... перед этим уже отсылается несколько пакетов. Цитата sploid @ В данном случае, до вызова m_Condition.wakeAll(); необходимо гарантировать, что рабочий поток стоит на строчке m_Condition.wait(&m_Mutex). Да, читал про это... попробую. Добавлено там в примере действительно подобная ситаация, но решается она как-то "криво", через sleep... И каунтер while (count > 0) { mutex.unlock(); sleep(1); mutex.lock(); } Добавлено я не понимаю вот чего!!: мьютекс же типа критической секции. все тело цикла while защищено ей (QMutexLocker), то есть EnqueuePacket должен висеть на lock, пока не отработает тело while??? ПО идее... |
Сообщ.
#4
,
|
|
|
ты не понял немного, если мьютекс еще не залочился, т.е. один поток стоит где комментарий "ТУТ СТОИМ, ЕЩЕ МЬЮТЕКС НЕ ЗАБЛОКИРОВАЛСЯ"
void QOutgoingPacketsThread::run() { while (true) { // ТУТ СТОИМ, ЕЩЕ МЬЮТЕКС НЕ ЗАБЛОКИРОВАЛСЯ QMutexLocker locker(&m_Mutex); // Деструктор класса QMutexLocker -- отпирает мьютекс m_Condition.wait(&m_Mutex); // ожидание неполнения очереди, ЗАВИСОН while (m_Packets.size()) { QNetPacket p = m_Packets[0]; if (p.empty()) { return; // end work } emit send_packet(p); m_Packets.erase(m_Packets.begin()); } } } а второй поток в вызове функции EnqueuePacket(const QNetPacket &packet). Еще мы еще не встали на wait, но пакет уже лежит в очереди, то в очереди будет один пакет так и лежать, пока не придет следующий пакет. А в этом месте бывает всегда, после обработки очередной порции пакетов. Причем данная ситуация ОЧЕНЬ вероятна!!! Получается так: 1. пришел пакет, началась дальше работа после функции m_Condition.wait(&m_Mutex) 2. во время обработки пришел очередной пакет и встал на мьютексе в строчке void QOutgoingPacketsThread::EnqueuePacket(const QNetPacket &packet) { m_Mutex.lock(); 3. обработка пакетов закончена и локер освобождает мьютекс QMutexLocker locker(&m_Mutex); 4. после освобождения мьютекса оживает первый поток, который кладет пакеты и в данный момент стоит на мьютексе ( этап 2 ), лочит мьютекс, кладет в очередь следующий пакет, освобождает мьютекс. 5. обрабатывающий поток лочит мьютекс QMutexLocker locker(&m_Mutex); и встает на вайте. Получается что 1 пакет в очереди висит. |
Сообщ.
#5
,
|
|
|
sploid
но ведь событие сигнализированно в wakeAll (1 поток), значит wait по идее не должен висеть... (во втором) Добавлено я так понимаю там событие с автосбросом... |
Сообщ.
#6
,
|
|
|
когда доходит до строчки
m_Condition.wait(&m_Mutex); мьютекс должен быть залочен и он разлочивается, поэтому входить в функцию EnqueuePacket после того как сработает m_Condition.wait(&m_Mutex); мьютекс залочивается. т.е. 1. стоит на вейт. 2. срабатывает wakeAll, но обрабатывающий поток дальше не идет, т.к. мьютекс еще залочен!!!! 3. разлочивается мьютекс в функции EnqueuePacket. 4. обрабатывающий поток лочит мьютекс при выходе из функции m_Condition.wait(&m_Mutex). как часто это у тебя происходит? можно вставить отладочную печать и посмотреть на ход работы? попробуй так ( не уверен что будет работать, но попробовать стоит ) void QOutgoingPacketsThread::run() { QMutexLocker locker(&m_Mutex); // Деструктор класса QMutexLocker -- отпирает мьютекс while (true) { m_Condition.wait(&m_Mutex); // ожидание неполнения очереди, ЗАВИСОН while (m_Packets.size()) { QNetPacket p = m_Packets[0]; if (p.empty()) { return; // end work } emit send_packet(p); m_Packets.erase(m_Packets.begin()); } } } |
Сообщ.
#7
,
|
|
|
sploid
происходит очень редко, с трудом удается словить. Цитата sploid @ можно вставить отладочную печать и посмотреть на ход работы? можно, надеюсь удасться воспроизвести... а если m_Condition.wakeAll(); делать после unlock() ? |
Сообщ.
#8
,
|
|
|
Я c QT не знаком, но имею много других хороших товарищей
Здесь меняем немножко: void QOutgoingPacketsThread::EnqueuePacket(const QNetPacket &packet) { m_Mutex.lock(); m_Packets.push_back(packet); m_Mutex.unlock(); m_Condition.wakeAll(); } Здесь переделываем немного: void QOutgoingPacketsThread::run() { while (true) { m_Condition.wait(); m_Mutex.lock(); while (m_Packets.size()) { QNetPacket p = m_Packets[0]; if (p.empty()) return; // end work emit send_packet(p); m_Packets.erase(m_Packets.begin()); } m_Mutex.unlock(); } } Мораль сего кода не использует всяческие фичи QT с которым мне влом разбираться, но данный алгоритм рабочий Если в run() после вызова m_Condition.wait() событие сьросится в несигнальное (так должно быть), то всё будет ок. На самом деле, я бы здесь соптимизировал немного алгоритм: emit send_packet(p); не обязательно вызывать внутри мьютекса, т.к. это снижает производительность из-за повышения времени лока и потенциально может вызвать дедлок, в зависимости от того что делается send_packet. Так что предлагаю внутри while наполнить временную очередь пакетов, а после m_Mutex.unlock(); их отправить. Добавлено Ещё один вариант run для версии 4.5 void QOutgoingPacketsThread::run() { forever { m_Mutex.lock(); m_Condition.wait(&m_Mutex); while (m_Packets.size()) { QNetPacket p = m_Packets[0]; if (p.empty()) { m_Mutex.unlock(); return; // end work } emit send_packet(p); m_Packets.erase(m_Packets.begin()); } m_Mutex.unlock(); } } |
Сообщ.
#9
,
|
|
|
rastoman твой вариант отличается только тем, что у тебя wakeAll вынесен из лока мьютекса. На мой взгляд это погоды не делает и могут возникнуть теже проблемы.
|
Сообщ.
#10
,
|
|
|
sploid, ты прав, только этим и отличается, но в данной реализации это важно.
В любой синхронизации важно избежать дедлоков - предусмотреть это порой бывает непросто. Здача же здесь стоит и в самом деле предельно простая - один поток пишет, второй читает. Задачу общего доступа к общему ресурсу решаем с помощью мьютекса, а уведомляем о событии изменения ресурса с помощью QWaitCondition. Проблема растёт из-за того, что в QT 4.5 QWaitCondition почему-то должен быть повязан с мьютексом. Так вот, если m_Condition.wakeAll() вызывать внутри мьютекса, то может быть дедлок, т.к. другой поток сразу или очень быстро (зависит от реализации в QT) также попытается залочить мьютекс. Потому правильнее сначала разлочить мьютекс, а потом вызовом m_Condition.wakeAll() позволить второму потоку на выходе из m_Condition.wait(&m_Mutex) залочить этот мьютекс, в противном случае может m_Condition.wait может зависнуть, что и случилось у автора треда. При полностью аналогичной реализации на WinApi было бы не важно где вызывать аналог wakeAll (SetEvent), а в QT, боюсь, разница есть. |
Сообщ.
#11
,
|
|
|
rastoman объясни, почему
Цитата Так вот, если m_Condition.wakeAll() вызывать внутри мьютекса, то может быть дедлок, если смотреть их пример http://doc.trolltech.com/4.5/threads-waitconditions.html то там и wait и wakeAll реализованы внутри локов мьютекса, из кода примера: void Consumer::run() { for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == 0) bufferNotEmpty.wait(&mutex); mutex.unlock(); fprintf(stderr, "%c", buffer[i % BufferSize]); mutex.lock(); --numUsedBytes; bufferNotFull.wakeAll(); mutex.unlock(); } fprintf(stderr, "\n"); } ты, кстати, не правильно понял проблему!! у человека не дед-лок, а один не обработанный пакет в очереди зависает!!! |
Сообщ.
#12
,
|
|
|
Цитата sploid @ rastoman объясни, почему Т.к. в подходе к реализации задачи ошибок не вижу, то делаю предположение на две вещи: 1. Особенность реализации QWaitCondition.wakeAll и QWaitCondition.wait 2. Кривость QMutexLocker или совместного использования QMutexLocker и QWaitCondition. Больше склоняюсь к первому варианту. Что касается примеров - очень часто бывает, что примеры содержат ошибки. Слово "ЗАВИСОН" может означать несколько вещей: зацикливание или дедлок. Что имел ввиду автор - фиг знает. То, что в очереди 1 пакет (кстати, что за пакет был, нормальный или пустой?) при этом - очевидно, он туда успел один добавиться и всё, дальше затык...: Как я понимаю порядок вполнения по потокам: 1. Был вызван EnqueuePacket: 1.1 мьютекс лочится 1.2 в очередь добавлен 1 пакет 1.3 QWaitCondition.wakeAll -> должен был последовать возврат из QWaitCondition.wait (см. п.2.3) 1.4 мьютекс разлочивается 2. Внутренний поток: 2.1 Мьютекс лочится 2.2 На вызове QWaitCondition.wait мьютекс разлочивается и поток засыпает 2.3 Из-за вызова QWaitCondition.wakeAll из другого потока (см. п.1.3): 2.3.1 Поток просыпается 2.3.2 Мьютекс лочится 2.3.3 Возврат управления из метода QWaitCondition.wait 2.3.4 Из очереди обрабатываются и удаляются все пакеты 2.4 Мьютекс разлочивается Однако дальше п.2.3 выполнение, как я понял, не пошло. Точнее, оно стопудово не дошло до 2.3.4. Первый поток ещё не достиг выполнения 1.4, а второй поток находится в стадии 2.3.2 (не в 2.3.3 - там подвисать негде) (Мьютекс лочится) и именно на этом пункте подвисает - ждёт, пока мьютекс будет освобождён первым потоком. Так вот полагаю, что возврата из QWaitCondition.wakeAll на самом деле не последовало из-за того, что, QWaitCondition.wakeAll во втором потоке подвис и, следовательно, первый поток не дошёл до 1.4. Это и есть дедлок. Если я прав - проблема будет исправлена, если я не прав, значит проблема где-то внутри QT или класс как-то криво используется. |
Сообщ.
#13
,
|
|
|
rastoman Если я правильно понял, то поток виснет в функции QWaitCondition.wakeAll, пока другие потоки не выйдут из QWaitCondition.wait?
|
Сообщ.
#14
,
|
|
|
Проблема в том, что поток чего-то ждет на wait, хотя на самом деле в очереди есть один пакет!
|
Сообщ.
#15
,
|
|
|
Цитата sploid @ rastoman Если я правильно понял, то поток виснет в функции QWaitCondition.wakeAll, пока другие потоки не выйдут из QWaitCondition.wait? Если бы я это знал... я бы тогда точно сказал, что я прав. А так это всё мои предположения. Других вариантов у меня попросту нет. Если бы мне кто скинул сорцы QWaitCondition.wakeAll и QWaitCondition.wait - сказал бы точнее. Да и вообще, я в винапи SetEvent (аналог wakeAll) всегда вызываю после разлочивания мьютекса/критической секции по одной простой причине - снижается вероятность борьбы за ресурс на объекте синхронизации, что, касательно критической секции, положительно влияет на производительность. Кстати, т.к. поток всего один, может стоит использовать QWaitCondition.wakeOne? |