如何修复“在 Qt 中将两个定时器变为一个函数,使用 qmutex 将 qeventloop 进行睡眠”
Posted
技术标签:
【中文标题】如何修复“在 Qt 中将两个定时器变为一个函数,使用 qmutex 将 qeventloop 进行睡眠”【英文标题】:How to fix "In Qt two timer to one function, use qmutex will qeventloop for sleep" 【发布时间】:2019-10-23 21:53:21 【问题描述】:我有一些代码使用 qtcpsocket 来读写, 写-->睡眠-->读; 并且 ui 有 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...";
我想我跑出去放: 登录好玩... 被锁定在乐趣... 退出有趣... 登录好玩... 被锁定在乐趣中... 注销好玩...
但它像这样运行: 登录好玩... 被锁定在乐趣... 登录乐趣.... - - - - - - - - - - - - - - - - -不再 - - - - - - - - ------
【问题讨论】:
它运行起来就像“登录乐趣...”;“获得锁定乐趣...”;“登录乐趣...”; 你的问题不是很清楚。是否要同步函数FunCal()
,使其一次最多执行一次?
【参考方案1】:
恕我直言,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
的使用就没有多大意义。在单线程中,一个简单的bool
变量也可以,因为在单线程中不可能有并发访问。
无论 OP 试图在此代码中实现什么:关于 Qt 强制/要求的基于事件的编程,问题只是建模错误。
在单线程中,一个函数不能输入两次accept by
-
(直接或间接)递归调用
对触发的中断处理程序的调用。
除了 2. 之外(与 OPs Qt 问题无关),由于建立了第二个(嵌套)事件循环,递归调用显式发生。没有这个,整个(互斥)锁定是不必要的,也应该被删除。
要了解一般的基于事件的编程——它在 Qt 文档中有所描述。 The Event System.
另外,我找到了 Jasmin Blanchette 的 Another Look at Events,恕我直言,它对 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;
;
一个示例对象
类属性成员: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。我不会再这样做了。)
【讨论】:
@danny 实际上,我的回答描述了你做错了什么,但没有解释如何做对。那是因为我无法在您的问题中识别出您的实际问题(您实际尝试解决的问题)。我假设你想根据一些时间来实现一系列动作。我会使用一个状态的积分变量来做到这一点。 On timeout:根据状态做动作,设置下一个状态,重启定时器(有,可能,不同的时间间隔),并从信号处理程序返回。本质上是信号处理程序在处理后立即返回。否则,事件循环将停止。以上是关于如何修复“在 Qt 中将两个定时器变为一个函数,使用 qmutex 将 qeventloop 进行睡眠”的主要内容,如果未能解决你的问题,请参考以下文章