恕我直言,OP的问题源于一个基本的误解:
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
只要没有竞争线程,使用 a就没有多大意义。在单个线程中,一个简单的bool
变量也可以,因为在单个线程中不可能有并发访问。
无论 OP 试图在这段代码中实现什么:关于 Qt 强制/要求的基于事件的编程,问题只是建模错误。
在单线程中,一个函数不能被输入两次
- (直接或间接)递归调用
- 触发中断处理程序的调用。
撇开 2. 不谈(与 OPs Qt 问题无关),由于建立了第二个(嵌套)事件循环,递归调用显式发生。没有这个,整个(互斥)锁定是不必要的,也应该被删除。
要了解一般的基于事件的编程——它在 Qt 文档中有所描述。事件系统。
此外,我还发现了 Jasmin Blanchette 的“Another Look at Events ”,恕我直言,它很好地介绍了 Qt 基于事件的执行是如何工作的。
笔记:
一旦涉及的对象和信号的数量变得足够大,基于事件的编程就会变得混乱。在调试我的 Qt 应用程序时,我不时注意到我没想到的递归。
一个简单的例子:一个值被改变并发出一个信号。其中一个插槽更新了一个 Qt 小部件,该小部件发出关于修改的信号。其中一个插槽会更新该值。因此,值被改变并发出信号......
要打破这种无限递归,std::lock_guard
可以将 a 与简单的 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; }
};
一个样本对象
- 类似属性的成员:
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 on 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
Live Demo on coliru
这类似于 OPs 案例中发生的情况,唯一的区别是在我的案例中,双重锁定违反了assert()
.
为了完成这一点,我也排除了锁守卫std::lock_guard<Lock> lock(_lockValue);
并得到以下输出:
execution expired
Live Demo on coliru
执行陷入了无限递归,在线编译器在一定时间后中止了它。(对不起,coliru。我不会再这样做了。)