简体   繁体   中英

How to fix “In Qt two timer to one function, use qmutex will qeventloop for sleep”

I have some code use qtcpsocket to write and read, write-->sleep-->read; and ui had 2 and more timer to use this function; by i want i run synchronous;so i add mutex to lock it; by it deadlock;

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...";
}

I Want i run and out put as: log in fun... getted lock in fun ... log out fun... log in fun... getted lock in fun... log out fun...

but It Run like this: log in fun... getted lock in fun ... log in fun.... ---------------------------------no more ---------------------

IMHO the issue of OP results from a basic misunderstanding:

QTimer doesn't introduce multithreading.
It's just a facility to queue events which will sent after a certain time.

That's why, the QEventLoop is necessary to make it running at all.

However, it's still a deterministic execution and this is what probably happens inside the code of OP:

  • pTimer->start(); → starts first timer
  • pTimer2->start(); → starts second timer
  • control flow returns to event loop of QApplication (not exposed in code)
  • first timer becomes due and calls MainWindow::FunCal()
  • qDebug()<<"log in fun..."; → output of log in fun...
  • QMutexLocker loc(&this->_mtx); this->_mtx becomes locked
  • qDebug()<<"getted lock in fun..."; → output of getted lock in fun...
  • loop.exec(); → enter a nested event loop (Nested event loops are allowed in Qt.)
  • second timer becomes due and calls MainWindow::FunCal() (Please, remember that it was immediately started after first with same interval time.)
  • qDebug()<<"log in fun..."; → output of log in fun...
  • QMutexLocker loc(&this->_mtx); → PROBLEM!

To illustrate it further, imagine the following call stack at this point (above called below):

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()

(Note: In reality, you will see much more entries in the call-stack which I considered as irrelevant details in this case.)

The problem is: This second call of MainWindow::FunCal() cannot lock the mutex because it is already locked. Hence, the execution is suspended until the mutex is unlocked but this will never happen. The locking of mutex happened in the same thread (in the first/outer call of MainWindow::FunCal() ). Unlocking would require to return from this point but it cannot because it's suspended due to the locked mutex.

If you think this sounds like a cat byting into its own tail – yes, this impression is right. However, the official term is Deadlock .

The usage of a QMutex doesn't make much sense as long as there are no competing threads. In a single thread, a simple bool variable would do as well because there are no concurrent accesses possible in a single thread.

Whatever OP tried to achieve in this code: Concerning the event-based programming forced/required by Qt, the problem is simply modeled wrong.

In single threading, a function cannot be entered twice accept by

  1. a (direct or indirect) recursive call
  2. a call out of a triggered interrupt handler.

Leaving the 2. aside (irrelevant for OPs Qt issue), the recursive call happens explicitly due to establishing a second (nested) event loop. Without this, the whole (mutex) locking is unnecessary and should be removed as well.

To understand event-based programming in general – it's described in the Qt doc. The Event System .

Additionally, I found Another Look at Events by Jasmin Blanchette which IMHO gives a nice little introduction into how the Qt event-based execution works.

Note:

Event-based programming can become confusing as soon as the amount of involved objects and signals becomes large enough. While debugging my Qt applications, I noticed from time to time recursions which I didn't expect.

A simple example: A value is changed and emits a signal. One of the slots updates a Qt widget which emits a signal about modification. One of the slots updates the value. Hence, the value is changed and emits a signal...

To break such infinite recursions, a std::lock_guard might be used with a simple 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 sample object with

  • a property-like member: bool _value
  • a simplified signal emitter: std::function<void()> sigValueSet
  • and a lock used to prevent recursive calls to 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();
    }
};

Finally, some code to force the lock into action:

#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';
}

To keep things short, I connected a slot to the signal which directly sets value again while the signal is emitted.

Output:

Object obj;
obj.value(): 0
obj.sigValueSet = [&](){ obj.setValue(obj.value()); };
obj.setValue(true);
obj.value(): 1

Live Demo on coliru

For a counter-example, I excluded the test if (_lockValue) return; and got the following output:

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

This is similar to what happened in OPs case with the only difference that in my case double-locking just violated the assert() .

To make this complete, I exluded the lock guard std::lock_guard<Lock> lock(_lockValue); as well and got the following output:

execution expired

Live Demo on coliru

The execution was trapped into an infinite recursion, and the online compiler aborted this after a certain time. (Sorry, coliru. I won't do it again.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM