Как исправить «В Qt два таймера для одной функции, используйте qmutex, чтобы qeventloop для сна» - PullRequest
0 голосов
/ 09 июня 2019

У меня есть код, использующий qtcpsocket для записи и чтения, записи -> sleep -> read;и у меня было 2 и более таймера для использования этой функции;я хочу запустить синхронно, поэтому я добавляю мьютекс, чтобы заблокировать его;по этому тупику;

qt4;qt5;

void MainWindow::Start()
{
    pTimer = new QTimer(this);
    pTimer->setInterval(100);
    connect(pTimer,SIGNAL(timeout()), this, SLOT(OnTimer()) );
    pTimer->start();
    pTimer2 = new QTimer(this);
    pTimer2->setInterval(100);
    connect(pTimer2,SIGNAL(timeout()), this, SLOT(OnTimer()) );
    pTimer2->start();
}

void MainWindow::OnTimer()
{
    FunCal();   // in my real code it will MyObj.DoSometing();
}
void MainWindow::FunCal()
{
    qDebug()<<"log in fun...";
    QMutexLocker loc(&this->_mtx);
    qDebug()<<"getted lock in fun...";
    QEventLoop loop;
    QTimer::singleShot(100, &loop, SLOT(quit()));
    loop.exec();
    qDebug()<<"log out fun...";
}

Хочу, чтобы я бегал и выходил, как: войти в систему веселья ... получить удовольствие в веселье ... выйти из игры весело ... войти в игру ... получить удовольствие в получить удовольствие... выйти из игры весело ...

но это работает так: войти в игру весело ... получить блокировку в удовольствие ... войти в удовольствие .... --------------------------------- не более ---------------------

1 Ответ

2 голосов
/ 09 июня 2019

ИМХО проблема ОП возникает из-за основного недопонимания:

QTimer не вводит многопоточность.Это просто возможность ставить в очередь события, которые будут отправляться через определенное время.

Вот почему QEventLoop необходим для его запуска вообще.

Однако, это все ещедетерминированное выполнение, и это, вероятно, происходит внутри кода OP:

  • pTimer->start(); → запускает первый таймер
  • pTimer2->start(); → запускает второй таймер
  • поток управления возвращается в цикл обработки событий QApplication (не отображается в коде)
  • первый таймер становится обязательным и вызывает MainWindow::FunCal()
  • qDebug()<<"log in fun..."; → вывод log in fun...
  • QMutexLocker loc(&this->_mtx);this->_mtx блокируется
  • qDebug()<<"getted lock in fun..."; → вывод getted lock in fun...
  • loop.exec(); → вводить вложенный цикл событий (В Qt допускаются вложенные циклы событий.)
  • секундный таймер истекает и вызывает MainWindow::FunCal() (Пожалуйста, помните, что он был запущен сразу после первого с тем же интервалом времени.)
  • qDebug()<<"log in fun..."; → вывод log in fun...
  • QMutexLocker loc(&this->_mtx); → ПРОБЛЕМА!

Для дальнейшей иллюстрации представим следующий стек вызововна данный момент (выше называется ниже):

QApplication::exec()
QEventLoop::exec()
QEventLoop::processEvents()
QTimer::timerEvent()
QTimer::timeOut()
MainWindow::onTimer()
MainWindow::FunCal()
QEventLoop::exec()
QTimer::timerEvent()
QTimer::timeOut()
MainWindow::onTimer()
MainWindow::FunCal()
QMutexLocker::QMutexLocker()
QMutex::lock()

(Примечание: в действительности вы увидите гораздо больше записей в стеке вызовов, которые в данном случае я считал неуместными деталями.)

Проблема в том, что этот второй вызов MainWindow::FunCal() не может заблокировать мьютекс, потому что он уже заблокирован.Следовательно, выполнение приостанавливается до тех пор, пока мьютекс не будет разблокирован, но этого никогда не произойдет.Блокировка мьютекса произошла в том же потоке (при первом / внешнем вызове MainWindow::FunCal()).Разблокировка потребует возврата с этой точки, но не может, потому что она приостановлена ​​из-за заблокированного мьютекса.

Если вы думаете, что это звучит как кошка, бьющаяся в собственный хвост - да, это впечатление правильное.Тем не менее, официальный термин - Deadlock .

Использование QMutex не имеет большого смысла, если нет конкурирующих потоков.В одном потоке простая переменная bool также будет работать, поскольку в одном потоке невозможен одновременный доступ.

Чего бы ни пытался достичь в этом коде OP: Относительно программирования на основе событий принудительный /требуемый Qt, проблема просто смоделирована неправильно.

В однопоточном режиме функция не может быть введена дважды, принимая

  1. рекурсивный вызов (прямой или косвенный)
  2. вызов из обработчика триггерных прерываний.

Оставляя 2. в стороне (не имеет значения для проблемы Qt OPs), рекурсивный вызов происходит явно из-за установления второго (вложенного) цикла событий.Без этого вся блокировка (мьютекс) не нужна и также должна быть удалена.

Для понимания программирования на основе событий в целом - это описано в документе Qt. Система событий .

Кроме того, я нашел Другой взгляд на события от Жасмин Бланшетт, который, по моему мнению, дает хорошийнебольшое введение в работу Qt на основе событий.

Примечание:

Программирование на основе событий может запутаться, как только количество задействованных объектов и сигналов станетдостаточно большой.Во время отладки приложений Qt я время от времени замечал рекурсии, которых я не ожидал.

Простой пример: значение изменяется и издает сигнал.Один из слотов обновляет виджет Qt, который выдает сигнал о модификации.Один из слотов обновляет значение.Следовательно, значение изменяется и издает сигнал ...

Чтобы прервать такие бесконечные рекурсии, std::lock_guard может использоваться с простым DIY class Lock:

#include <iostream>
#include <mutex>
#include <functional>
#include <cassert>

// a lock class
class Lock {
  private:
    bool _lock;

  public:
    Lock(): _lock(false) { }
    ~Lock() = default;
    Lock(const Lock&) = delete;
    Lock& operator=(const Lock&) = delete;

    operator bool() const { return _lock; }
    void lock() { assert(!_lock); _lock = true; }
    void unlock() { assert(_lock); _lock = false; }
};

Aпример объекта с

  • свойственным элементу свойства: bool _value
  • упрощенным источником сигнала: std::function<void()> sigValueSet
  • и блокировкой, используемой для предотвращения рекурсивных вызововsetValue(): Lock _lockValue
// a sample data class with a property
class Object {
  private:
    bool _value; // a value
    Lock _lockValue; // a lock to prevent recursion

  public:
    std::function<void()> sigValueSet;

  public:
    Object(): _value(false) { }
    bool value() const { return _value; }
    void setValue(bool value)
    {
      if (_lockValue) return;
      std::lock_guard<Lock> lock(_lockValue);
      // assign value
      _value = value;
      // emit signal
      if (sigValueSet) sigValueSet();
    }
};

Наконец, некоторый код для активации блокировки:

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__

int main()
{
  DEBUG(Object obj);
  std::cout << "obj.value(): " << obj.value() << '\n';
  DEBUG(obj.sigValueSet = [&](){ obj.setValue(obj.value()); });
  DEBUG(obj.setValue(true));
  std::cout << "obj.value(): " << obj.value() << '\n';
}

Для краткости я подключил слот к сигналу, который непосредственно устанавливает значение, пока сигнал излучается.

Выход:

Object obj;
obj.value(): 0
obj.sigValueSet = [&](){ obj.setValue(obj.value()); };
obj.setValue(true);
obj.value(): 1

Live Demo на coliru

Для контрпримера я исключил тест if (_lockValue) return; и получил следующий вывод:

a.out: main.cpp:18: void Lock::lock(): Assertion `!_lock' failed.
Object obj;
obj.value(): 0
obj.sigValueSet = [&](){ obj.setValue(obj.value()); };
obj.setValue(true);
bash: line 7: 12214 Aborted                 (core dumped) ./a.out

Демонстрация в реальном времени на coliru

Это похоже на то, что произошло в случае с OP, с той лишь разницей, что в моем случае двойная блокировка только нарушилаassert().

Для этого я также исключил защиту замка std::lock_guard<Lock> lock(_lockValue); и получил следующий вывод:

execution expired

LiveДемонстрация по coliru

Выполнение было заточено в бесконечную рекурсию, и онлайн-компилятор прервал это через некоторое время.(Извините, колиру. Я не буду делать это снова.)

...