0

I am working on a data logger in QT framework. I Intend to save log strings to files and print to the console in a separate watcher thread. In that separate thread I need to watch my QStringList for new items added. If there are new items I deque them and log. I was wondering what is the mechanism used for this in Qt framework. In STD lib i used condition_variable for this task like this:

/*!
 * \brief puts a \ref logline_t object at the end of the queue
 * @param s object to be added to queue
 */
void CLogger::push_back(logline_t* s)
{
    unique_lock<mutex> ul(m_mutex2);
    s->queSize = m_data.size();
    m_data.emplace_back(move(s));
    m_cv.notify_all();
}

/*!
 * \brief takes a \ref logline_t object from the beggining of the queue
 * @return first \ref logline_t object
 */
CLogger::logline_t CLogger::pop_front()
{
    unique_lock<mutex> ul(m_mutex2);
    m_cv.wait(ul, [this]() { return !m_data.empty(); });

    assert(m_data.front());
    logline_t retVal = move(*m_data.front());

    delete m_data.front();
    m_data.front() = NULL;

    m_data.pop_front();

    return retVal;
}

m_cv is the conditional variable object. How can this functionality be acquired with QT? I bet there is a lot better way :). I would appreciate all help. Ps: I know pointer functions parameters are not asserted, this is an old code... :P

Łukasz Przeniosło
  • 2,725
  • 5
  • 38
  • 74
  • 2
    You can use the standard library/boost with Qt, there's nothing wrong with using a condition variable. but if you want to go all-Qt then use signals and slots for this. This may be slower, though. You'd have to benchmark. – krzaq Oct 14 '16 at 19:14
  • I didnt want to touch boost for this one. Just like you said I wanted to see either Qt gives something nice here. With signal/ slots, even though it seems obvious at first, I wonder either it is the right choice for this kind of thing. I am not saying one is obligated to use signals/ slots only for gui, but maybe I thought there is some dedicated watcher in qt framework, or maybe something simmilar to what freeRTOS offers (When you add item to queue, waiting task is being unlocked). Also I am not sure signals/ slots are slower, there is always an argue about it. – Łukasz Przeniosło Oct 14 '16 at 19:19
  • 2
    Signals and slots make for great synchronization, gui or not, if you're not overly concerned with performance (although they're not terrible, either). I use them for passing messages between threads in production (console app running as a service) for years now and they're fine. You don't get FIFO guarantee with them, but in practice you get that anyway. If there's an another synchronization api from Qt then I don't know of it. – krzaq Oct 14 '16 at 19:22
  • In the end I would just have to signal when I append something to the QStringList. Since I append its a natural FIFO i gues. I think ill stick to it then. Thanks :). – Łukasz Przeniosło Oct 14 '16 at 19:24
  • When I think of it again, I am not sure how to implement this. With conditional variable I know that the if wait is called in a separate thread, the read happens in that thread... Lets say I append a string to QStringList and emit a signal. How can I call the slot in a different thread, not the one that emitted the signal? Also, it would be at hand if the slot thread was lower priority than emitter thread. Can qt do that? – Łukasz Przeniosło Oct 14 '16 at 20:45
  • `QObject::connect` is magical. By default it uses `Qt::AutoConnection` which means "call now if same thread, magically thread-safe schedule the slot to be called in `receiver` thread's event loop". When you `emit` a signal, it will do the right thing™ for all connected receivers. Gotchas (if you can call them that): 1) you must create the receiver object in its thread, or use `QObject::moveToThread()` function. 2) the receiver thread must have an event loop running – krzaq Oct 14 '16 at 20:48
  • 1
    I can make an example if you want, I guess. – krzaq Oct 14 '16 at 20:48
  • I would really appreciate that. I remember i used the functionality you mentioned- `QObject::moveToThread()`. But in this situation its hard for me to understand one concept- with wait (or other similar object) you are in a loop that yelds its operation for other threads. With signal- slot, how can I know when will the slot be called? – Łukasz Przeniosło Oct 14 '16 at 20:56
  • Posted as an answer. It's quite long anyway. – krzaq Oct 14 '16 at 21:18

3 Answers3

2

There are a couple of ways of doing the notification in Qt.

Signals and Slots

Signals and slots can be sent between threads, when making the connection, you set the connection type to Qt::QueuedConnection or Qt::BlockingQueuedConnection.

You may want to create a wrapper class around the QStringList so that modifications can trigger the signals that other classes listen for.

QMutex and QWaitCondition

You can use the Qt thread synchronisation classes QMutex and QWaitCondition to do the classic manual synchronisation like you have done previously. When using QMutex, the QMutexLocker is useful for scope lock and release of a QMutex.

Silas Parker
  • 8,017
  • 1
  • 28
  • 43
1

Here's an example of doing this signals and slots. You may want to do your own benchmarks to test whether this fits your needs. Please also note that while signals and slots guarantee thread-safety, they don't guarantee that the messages will appear in the same order they were sent. That being said, I've used this mechanism for years and it has yet to happen for me.

First, create a couple of classes:

Loggee

// loggee.hpp
#ifndef LOGGEE_HPP
#define LOGGEE_HPP

#include <QObject>

class Loggee : public QObject
{
    Q_OBJECT
public:
    using QObject::QObject;


    void someEventHappened(int id);

signals:

    void newLogLineNotify(QString const&);
};

#endif // LOGGEE_HPP

and the .cpp file:

#include "loggee.hpp"

void Loggee::someEventHappened(int id)
{
    auto toLog = QString::number(id);
    emit newLogLineNotify(toLog);
}

Logger

#ifndef LOGGER_HPP
#define LOGGER_HPP

#include <QObject>

class Logger : public QObject
{
    Q_OBJECT
public:
    using QObject::QObject;

signals:

public slots:

    void onLogEventHappened(QString const&);

};

#endif // LOGGER_HPP

and the .cpp file:

#include <QDebug>
#include <QThread>

#include "logger.hpp"

void Logger::onLogEventHappened(QString const& str)
{
    qDebug() << QThread::currentThreadId() << str;
}

the rest

#include <QDebug>
#include <QThread>
#include <QCoreApplication>
#include <QTimer>
#include "logger.hpp"
#include "loggee.hpp"

int main(int argc, char** argv)
{
    QCoreApplication a(argc, argv);
    QThread t;
    t.start();

    Logger foo;
    Loggee bar;
    foo.moveToThread(&t);

    QObject::connect(&bar, &Loggee::newLogLineNotify,
                     &foo, &Logger::onLogEventHappened,
                     Qt::AutoConnection);

    qDebug() << "Current thread id: " << QThread::currentThreadId();

    bar.someEventHappened(42);

    QTimer::singleShot(3000, [&]{ bar.someEventHappened(43); });

    return a.exec();
}

In this demo, I create a QThread and a Logger, move handling of this new Logger's slots to this new thread's event loop. Then I connect Loggee's signal with Logger's slot using Qt::AutoConnection. This is the default but I stated it explicitly to show that you can change this (i.e. to Qt::QueuedConnection which would queue the execution of the slot even if both threads lived in the same thread).

Then I cause the Loggee to emit¹ a singal. It gets properly scheduled and causes the logging slot to be executed in the receiver's thread.

¹ emit is #define emit /*null*/, so you can omit it if you want

krzaq
  • 16,240
  • 4
  • 46
  • 61
  • Thank you for the explanation. I get the idea now and I will be able to wrap this up in a logger singleton class. Could you only tell me either you have any control over threads priority? For example, when creating `QThread t`, can you make it very low priority? – Łukasz Przeniosło Oct 14 '16 at 21:27
  • 1
    There is [`QThread::setPriority`](http://doc.qt.io/qt-5/qthread.html#setPriority). I have never used it, but it seems to be exactly what you want. – krzaq Oct 14 '16 at 21:31
1

If you are just looking for the Qt equivalents to the classes you have been using:

std::mutex -> QMutex
std::condition_variable -> QWaitCondition
std::unique_lock -> QMutexLocker
Kevin Krammer
  • 5,159
  • 2
  • 9
  • 22