На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела *nix / gcc / Eclipse / Qt / wxWidgets / GTK+
  • При создании темы ОБЯЗАТЕЛЬНО указывайте версию тулкита / библиотеки / компилятора.
  • Перед тем как задать вопрос, сформулируйте его правильно, чтобы вас могли понять.
  • Нарушение Правил может повлечь наказание со стороны модераторов.


Полезные ссылки:
user posted image Boost по-русски
user posted image Qt по-русски
Модераторы: archimed7592
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
> [QT] Синхронизация потоков , рызрыв мозга, помогите советом
    Ситуация супер простая! Все лишь разребание очереди пакетов.
    Бывает редко ситуация, когда поток зависает на строке ЗАВИСОН, хотя на самом деле в очереди есть 1 элемент. Подскажите советом.

    Привожу полный код.

    ExpandedWrap disabled
      // хидер
       
      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();
      }
      если я правильно понимаю, то если в начале вызвать функцию EnqueuePacket(const QNetPacket &packet), а только после этого запустить поток, то будет зависон пока не придет следующий пакет.
      Соответственно, если мы стоим где-то тут:
      ExpandedWrap disabled
        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
      Сообщение отредактировано: sploid -
        sploid
        мысль понял, но в данном случае, есть 100% гарантия что поток уже запущен... перед этим уже отсылается несколько пакетов.

        Цитата sploid @
        В данном случае, до вызова m_Condition.wakeAll(); необходимо гарантировать, что рабочий поток стоит на строчке m_Condition.wait(&m_Mutex).

        Да, читал про это... попробую.

        Добавлено
        там в примере действительно подобная ситаация, но решается она как-то "криво", через sleep... И каунтер

        ExpandedWrap disabled
          while (count > 0) {
                   mutex.unlock();
                   sleep(1);
                   mutex.lock();
               }


        Добавлено
        я не понимаю вот чего!!: мьютекс же типа критической секции. все тело цикла while защищено ей (QMutexLocker), то есть EnqueuePacket должен висеть на lock, пока не отработает тело while??? ПО идее...
        Сообщение отредактировано: Мальчиш -
          ты не понял немного, если мьютекс еще не залочился, т.е. один поток стоит где комментарий "ТУТ СТОИМ, ЕЩЕ МЬЮТЕКС НЕ ЗАБЛОКИРОВАЛСЯ"
          ExpandedWrap disabled
            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. во время обработки пришел очередной пакет и встал на мьютексе в строчке
          ExpandedWrap disabled
            void QOutgoingPacketsThread::EnqueuePacket(const QNetPacket &packet)
            {
                m_Mutex.lock();

          3. обработка пакетов закончена и локер освобождает мьютекс
          ExpandedWrap disabled
            QMutexLocker locker(&m_Mutex);

          4. после освобождения мьютекса оживает первый поток, который кладет пакеты и в данный момент стоит на мьютексе ( этап 2 ), лочит мьютекс, кладет в очередь следующий пакет, освобождает мьютекс.
          5. обрабатывающий поток лочит мьютекс
          ExpandedWrap disabled
            QMutexLocker locker(&m_Mutex);

          и встает на вайте.

          Получается что 1 пакет в очереди висит.
            sploid
            но ведь событие сигнализированно в wakeAll (1 поток), значит wait по идее не должен висеть... (во втором)

            Добавлено
            я так понимаю там событие с автосбросом...
              когда доходит до строчки
              m_Condition.wait(&m_Mutex);
              мьютекс должен быть залочен и он разлочивается, поэтому входить в функцию
              EnqueuePacket
              после того как сработает
              m_Condition.wait(&m_Mutex);
              мьютекс залочивается.

              т.е.
              1. стоит на вейт.
              2. срабатывает wakeAll, но обрабатывающий поток дальше не идет, т.к. мьютекс еще залочен!!!!
              3. разлочивается мьютекс в функции EnqueuePacket.
              4. обрабатывающий поток лочит мьютекс при выходе из функции m_Condition.wait(&m_Mutex).

              как часто это у тебя происходит? можно вставить отладочную печать и посмотреть на ход работы?
              попробуй так ( не уверен что будет работать, но попробовать стоит )
              ExpandedWrap disabled
                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());
                        }
                    }
                }
                sploid
                происходит очень редко, с трудом удается словить.

                Цитата sploid @
                можно вставить отладочную печать и посмотреть на ход работы?

                можно, надеюсь удасться воспроизвести...

                а если m_Condition.wakeAll(); делать после unlock() ?
                  Я c QT не знаком, но имею много других хороших товарищей :)

                  Здесь меняем немножко:
                  ExpandedWrap disabled
                    void QOutgoingPacketsThread::EnqueuePacket(const QNetPacket &packet)
                    {
                        m_Mutex.lock();
                        m_Packets.push_back(packet);
                        m_Mutex.unlock();
                        m_Condition.wakeAll();
                    }


                  Здесь переделываем немного:
                  ExpandedWrap disabled
                    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 :)

                  ExpandedWrap disabled
                    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();
                        }
                    }
                  Сообщение отредактировано: rastoman -
                    rastoman твой вариант отличается только тем, что у тебя wakeAll вынесен из лока мьютекса. На мой взгляд это погоды не делает и могут возникнуть теже проблемы.
                      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, боюсь, разница есть.
                      Сообщение отредактировано: rastoman -
                        rastoman объясни, почему
                        Цитата
                        Так вот, если m_Condition.wakeAll() вызывать внутри мьютекса, то может быть дедлок,


                        если смотреть их пример http://doc.trolltech.com/4.5/threads-waitconditions.html то там и wait и wakeAll реализованы внутри локов мьютекса, из кода примера:
                        ExpandedWrap disabled
                          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");
                           }


                        ты, кстати, не правильно понял проблему!!
                        у человека не дед-лок, а один не обработанный пакет в очереди зависает!!!
                          Цитата 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 или класс как-то криво используется.
                            rastoman Если я правильно понял, то поток виснет в функции QWaitCondition.wakeAll, пока другие потоки не выйдут из QWaitCondition.wait?
                              Проблема в том, что поток чего-то ждет на wait, хотя на самом деле в очереди есть один пакет!
                                Цитата sploid @
                                rastoman Если я правильно понял, то поток виснет в функции QWaitCondition.wakeAll, пока другие потоки не выйдут из QWaitCondition.wait?


                                Если бы я это знал... :) я бы тогда точно сказал, что я прав. А так это всё мои предположения. Других вариантов у меня попросту нет.
                                Если бы мне кто скинул сорцы QWaitCondition.wakeAll и QWaitCondition.wait - сказал бы точнее.
                                Да и вообще, я в винапи SetEvent (аналог wakeAll) всегда вызываю после разлочивания мьютекса/критической секции по одной простой причине - снижается вероятность борьбы за ресурс на объекте синхронизации, что, касательно критической секции, положительно влияет на производительность.

                                Кстати, т.к. поток всего один, может стоит использовать QWaitCondition.wakeOne?
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0592 ]   [ 17 queries used ]   [ Generated: 28.03.24, 21:54 GMT ]