ИМХО проблема ОП возникает из-за основного недопонимания:
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, проблема просто смоделирована неправильно.
В однопоточном режиме функция не может быть введена дважды, принимая
- рекурсивный вызов (прямой или косвенный)
- вызов из обработчика триггерных прерываний.
Оставляя 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
Выполнение было заточено в бесконечную рекурсию, и онлайн-компилятор прервал это через некоторое время.(Извините, колиру. Я не буду делать это снова.)