如何修复“在 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-&gt;start(); → 启动第一个计时器 pTimer2-&gt;start(); → 启动第二个计时器 控制流返回QApplication的事件循环(未在代码中公开) 第一个计时器到期并调用MainWindow::FunCal() qDebug()&lt;&lt;"log in fun...";log in fun... 的输出 QMutexLocker loc(&amp;this-&gt;_mtx);this-&gt;_mtx 被锁定 qDebug()&lt;&lt;"getted lock in fun...";getted lock in fun... 的输出 loop.exec(); → 进入嵌套事件循环(Qt 中允许嵌套事件循环。) 第二个计时器到期并调用MainWindow::FunCal()(请记住,它是在第一个计时器之后立即以相同的间隔时间启动的。) qDebug()&lt;&lt;"log in fun...";log in fun... 的输出 QMutexLocker loc(&amp;this-&gt;_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&lt;void()&gt; 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&lt;Lock&gt; lock(_lockValue); 并得到以下输出:

execution expired

Live Demo on coliru

执行陷入无限递归,并且在线编译器在一段时间后中止了此操作。 (对不起,coliru。我不会再这样做了。)

【讨论】:

@danny 实际上,我的回答描述了你做错了什么,但没有解释如何做对。那是因为我无法在您的问题中识别出您的实际问题(您实际尝试解决的问题)。我假设你想根据一些时间来实现一系列动作。我会使用一个状态的积分变量来做到这一点。 On timeout:根据状态做动作,设置下一个状态,重启定时器(有,可能,不同的时间间隔),并从信号处理程序返回。本质上是信号处理程序在处理后立即返回。否则,事件循环将停止。

以上是关于如何修复“在 Qt 中将两个定时器变为一个函数,使用 qmutex 将 qeventloop 进行睡眠”的主要内容,如果未能解决你的问题,请参考以下文章

如何修复漏洞

如何修复WMI

PHP网站漏洞怎么修复 如何修补网站程序代码漏洞

如何修复这些漏洞? (npm audit fix 无法修复这些漏洞)

如何修复AppScan漏洞

如何在DOS环境下修复系统