0

我有一些代码使用qtcpsocket读写,write-->sleep-->read;并且 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...";
}

我希望我运行并输出为:登录乐趣...获得锁定乐趣...退出乐趣...登录乐趣...获得锁定乐趣...退出乐趣...

但它像这样运行:登录乐趣...获得锁定乐趣...登录乐趣.... ------------------------ - - - - -不再 - - - - - - - - - - -

4

1 回答 1

2

恕我直言,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 强制/要求的基于事件的编程,问题只是建模错误。

在单线程中,一个函数不能被输入两次

  1. (直接或间接)递归调用
  2. 触发中断处理程序的调用。

撇开 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。我不会再这样做了。)

于 2019-06-09T10:47:25.207 回答