Optimal way to establish large number of QTimers
-
Hi,
I'm building a Qt application for my embedded Linux data acquisition device. The device captures data from 64 inputs, each of which has a configurable data acquisition frequency (i.e. poll rate).
I've established a QVector<int> to hold the poll rates for each of the 64 inputs. E.g. inputRate()[0] provides the poll rate (in samples/sec) of input1. Similarly, I've created QTimer objects and timeout functions for each of the 64 inputs. E.g. input1Timer, input2Timer, etc. and input1Timeout(), input2Timeout(), etc.
The current implementation has 64 discrete QTimer events in my main.cpp file as follows:
this->input1Timer = new QTimer(this); this->input1Timer->setInterval((1000 / configurationFile->inputRate()[0])); connect(this->input1Timer, &QTimer::timeout, this, &Main::input1Timeout); this->input1Timer->start(); this->input2Timer = new QTimer(this); this->input2Timer->setInterval((1000 / configurationFile->inputRate()[1])); connect(this->input2Timer, &QTimer::timeout, this, &Main::input2Timeout); this->input2Timer->start(); .... this->input64Timer = new QTimer(this); this->input64Timer->setInterval((1000 / configurationFile->inputRate()[63])); connect(this->input64Timer, &QTimer::timeout, this, &Main::input64Timeout); this->input64Timer->start();
This approach is obviously about as non-optimal as you can get. How should I be approaching this? Should each timer (and corresponding timeout function) be in a separate thread? If so, should I use an external QThread class, or can I instantiate QThread objects in my main.cpp file?
A secondary thought/question: should/can I put the 64 QTimers in a QVector<QTimer>? It might not make any difference, but I'd like to capture the instantiation of the 64 timers in a loop, particularly if I'm creating QThreads directly in the main.cpp file.
-
Hi,
You can use a
QVector<QTimer *>
as you can't copy QObject based classes. You can loop on yourinputRate
returned value to generate your QTimer objects. -
@jars121 A thread would be a good idea if your main thread has a bunch of other stuff to do besides deal with the data timers or if the data acquisition takes more than 100 ms. If you have a GUI on your main thread then most definitely gather your data in a separate thread. My .02. ;)
-
Thanks @ambershark.
I certainly have other processes on the main thread, so I'd like to use separate threads if possible. Would you recommend a separate thread for each of the 64 timers, or a collective thread?
-
@jars121 It really depends on how much work you are doing. I definitely don't recommend 64 threads, like pretty much ever. However if they are doing a ton of work you may want a thread pool. I would say 4-8 threads max though.
From the sounds of it just a single thread to manage data would be fine though. If you have performance issues then you can always expand later.
-
I'm little confused as to how I should be approaching this, now that I've done more reading into QThreads, and am hoping for some input from those with more experience.
Of the 64 inputs to my embedded device (as described in the original post), there are 3 distinct types:
- Analogue
- Digital - On/Off
- Digital - Pulsed (measured in terms of duty cycle and frequency)
The 64 inputs are split into two physical banks of 32. 32 analogue inputs (through dual 16-channel ADCs) and 32 digital inputs. The 32 digital inputs are individually user configurable as either Digital - On/Off or Digital - Pulsed, such that all 32 digital inputs could be On/Off, all 32 digital inputs could be Pulsed, or any combination of both (i.e. 20 On/Off, 12 Pulsed).
As such, I think the best way to approaching this from a threading perspective, is to have 3 discrete classes. One class creates a QThread, on which all the analogue inputs are placed. Another class creates a QThread, on which all Digital - On/Off inputs are placed, with the final being a class with QThread for all Digital - Pulsed inputs.
Does this approach make sense?
In regards to QThreads in general, I'm reading mixed information in how best to use them. I've read that QThreads shouldn't be sub-classed, and I'm better to sub-class QObject and then create a QThread from within the QObject class. Should I just stick with the approach(es) outlined in the official Qt5.X documentation?
-
@jars121 So it really just depends on how much each of those inputs really needs to do on whether or not it needs it's own thread. It won't hurt to have 3 threads but you may be able to just get away with 1. I don't know enough about it to help you there, but you can always start with 1 and expand to 3 easily later on. Which brings me to ...
The way to use QThreads now-a-days is not to subclass them. Subclassing still has it's purposes and what you want to do may be one of the places where subclassing may be better. It all depends on how you design your "data reading" classes.
So basically you can use a QObject and just call
moveToThread
which will put all the signals/slots you connect on a separate thread. Including those timers we talked about.Or you can subclass and write your own handlers for start/quitting the thread and how it deal with signals and slots. I prefer the former as it's much less work and doesn't get as confusing to deal with.
Along those lines I just wrote a threading example for someone recently on the forums here. I will point you at the code for that so you can see how you can use QThread without subclassing.
https://forum.qt.io/topic/88317/custom-type-sending-classes-for-communication-between-threads/7
-
Brilliant, thanks again!
The QObject methodology certainly makes sense, so I'll give that a go to start with. I'm going to start off with 3 threads for the time being, and look to consolidate if need be.
I'll go through your linked example over the coming day or so, and report back with progress.
-
I've been giving this some further thought (haven't had a chance to implement the approach listed above as of yet), and was hoping to get some feedback.
The current approach, as per my original question/post, is to create a separate QTimer for each of the 64 inputs. What I've been thinking about, is rather having a single QTimer, the resultant timeout function for which then runs through each of the individual inputs' functions. For example, if I set the single QTimer to a timeout of 1/1000 seconds (1000Hz), each input function will be able to run at 1000Hz. If the specified poll frequency of a given input is only 500Hz, the main QTimer timeout function simply skips the call to the individual input function every second time the timeout is called.
Does that make sense? The process seems to be simpler, as you're dealing with a single QTimer rather than 64, but the actual overhead of using 64 individual QTimers might be entirely insignificant, I really don't know. I'm happy to progress down the path of 64 individual QTimers and threads, but wanted to pose this question before doing so.
-
@jars121 said in Optimal way to establish large number of QTimers:
The current approach, as per my original question/post, is to create a separate QTimer for each of the 64 inputs. What I've been thinking about, is rather having a single QTimer, the resultant timeout function for which then runs through each of the individual inputs' functions.
Having 1 timer will give you a lot less jitter than having 64 timers. If you know exactly how frequently you want each function to run, and if there is one "common denominator" between all the times (e.g. all timers are multiples of 10 ms), then consider using just one timer. There's one downside: It will be more tedious to add another timed function if the new loop period is not an integer multiple of your existing timer's period.
Before you continue though, there are two important details to know:
- What is your operating system?
- How much timing error can your app tolerate?
I ask this because for an OS that's not real-time (especially Windows), you are unlikely to be able to fire events at precisely 1 ms intervals.
-
Very valid points, thank you @JKSH
The OS will be Embedded Linux. I'm well aware that it's not a RTOS, so will work within the confines of a non-RTOS. I may well end up only being able to accurately use 2ms (500Hz), or less, but I want to establish/develop the most optimal approach to begin with, and then start testing the possible frequencies.
In terms of the timed functions and their reference to the 1ms QTimer, that's something I'll need to work around as well.
-
@jars121 said in Optimal way to establish large number of QTimers:
I may well end up only being able to accurately use 2ms (500Hz), or less, but I want to establish/develop the most optimal approach to begin with, and then start testing the possible frequencies.
I think @JKSH's point is that you really shouldn't expect jitters under about 10ms unless your system supports precise timers, which may or may not be the case. In any case, for polling, which you shouldn't use for time critical data anyway, that's more than enough. In principle I'd always opt for a single timer (i.e. a single file descriptor) that will give the "tics" and then in the timeout handler check whatever timeouts have expired to fire whatever events I want. You can, if needed, run an infinite event loop (do it in a separate thread) that will generate timeout events as fast as the system can process them by setting the timeout for the timer to
0
, i.e.timer->start(0)
, but this would eat up all the CPU time for the core the thread runs on so it might or might not be appropriate for your case. Consider the following example code (untested) for the single timer approach, you'd need to wrap it in a nice class though:QTimer timer; //< Master poll timer QVector<qint32> timeouts(...); //< Poll timeouts QVector<qint64> activated(timeouts.size(), QDateTime::currentMSecsSinceEpoch()); //< When was the last activation timer.start(100); QObject::connect(&timer, &QTimer::timeout, &timer, [&timeouts, &activated] () -> void { // Check each of the timeouts for expiration qint64 currentTime = QDateTime::currentMSecsSinceEpoch(); for (qint32 i = 0, size = timeouts.size(); i < size; i++) { if ((currentTime - activated[i]) / timeouts[i]) { activated[i] = currentTime; // ... We need to fire the handler for the i-th polling operation here } } });
-
Thank you @kshegunov that's very much in line with how I had planned on catering for inputs with varying poll frequencies :)
I've only just had time to get around to addressing this in my application, and I'm having some trouble with threads. Here's what I've got so far, which is working, but the timeout functions are running on the main thread rather than on separate threads. I understand why this is the case, but haven't been able to get the separate threads working (I've included a SIGNAL/SLOT connect below which I believe is the right way to approach it, but I'd appreciate some points/feedback).
//main.cpp qDebug() << "Main thread ID: "" << QThread::currentThread(); QThread* analogThread = new QThread(this); QTimer* analogTimer = new QTimer(); analogTimer->setInterval(1); connect(analogTimer, &QTimer::timeout, this, &Main::analogTimeout); analogTimer->start(); analogTimer->moveToThread(analogThread); QThread* digitalThread = new QThread(this); QTimer* digitalTimer = new QTimer(); digitalTimer->setInterval(1); connect(digitalTimer, &QTimer::timeout, this, &Main::digitalTimeout); digitalTimer->start(); digitalTimer->moveToThread(digitalThread); //The below commented lines represent how I believe the timers should be started within the thread //I would then remove the timer->start() calls above. //connect(analogThread, SIGNAL(analogStarted()), analogTimer, SLOT(analogTimeout())); analogThread->start(); //connect(digitalThread, SIGNAL(digitalStarted()), digitalTimer, SLOT(digitalTimeout())); digitalThread->start(); } void Main::analogTimeout() { qDebug() << "Analog: " << QThread::currentThread(); } void Main::digitalTimeout() { qDebug() << "Digital: " << QThread::currentThread(); }
As you would expect, the result at the moment is that the "Main thread", "analogTimeout" and "digitalTimeout" qDebug()'s all show execution within the same thread. I'm missing a key understanding here; how do I move the analogTimer and digitalTimer into analogThread and digitalThread respectively?
Thanks!
-
So you normally would call
moveToThread
before connecting your signals. However in your case that won't work. You are trying to move only a timer to a thread. What you want is moving a QObject based class to a thread. Then all the signals it handles will be on that thread.So in your case you would need some class that has your slots you want to handle the timer:
class TimerHandler : public QObject { Q_OBJECT public: TimerHandler(QObject *parent = nullptr) : QObject(parent) { } public slots: void timerTicked() { qDebug() << "ticked in thread " << QThread::currentThreadId(); } }; // ... QThread *analogThread = new QThread(); TimerHandler *analogTimerHandler = new TimerHandler(); analogTimerHandler->moveToThread(analogThread); QTimer *analogTimer = new QTimer(); analogTimer->setInterval(1); // this is a bad idea :) connect(analogTimer, SIGNAL(timeout()), analogTimerHandler, SLOT(timerTicked())); analogThread->start(); analogTimer->start();
Something like that should work. That code may not be 100% accurate since I just typed it from my head with no auto completion or a compile test, but look up syntax if I missed some and it should work. The idea is what you needed.
Also having a timer that tries to fire every 1ms is going to probably give you 100% cpu usage on the processor that thread is on. Not a great design. You're better off in a for loop in your thread that just sleeps for an interval you need (1ms?). However even a for loop with 1ms sleep while it will be less intense than a signal it will still more than likely cause 100% cpu usage on that cpu.
-
In addition to what @ambershark wrote, you should read this. The thread the object is assigned to is relevant to its slots, not so much to the signals it emits.
-
Thank you both, much appreciated as always. The 1ms aspect isn't a hard requirement, more a 'best case' if the CPU is able to support it. I now understand your approach, regarding moving of the QObject to the thread rather than the QTimer itself.
Having said that, I'm more inclined to put the acquisition functions in a loop on its thread as you mentioned. I'll take a timestamp at the start of each iteration of the loop, and then sleep for a calculated period of time at the end of the loop, based on the current time, the beginning time and the desired poll frequency. E.g.:
while (1) { timeStamp = currentTime(); //do stuff sleep((1/pollFrequency)-(currentTime() - timeStamp)); }
I'll give this approach a go and play with frequencies to see what the CPU can handle.