Home

Synchronizing read/write access to a file

This examples demonstrates the use of QtReadWriteMutex to synchronize access to a file.

It starts one thread that writes to a file, and multiple threads that read from the file. The threads post logging messages to the GUI thread that displays those messages in a text display. When the application is terminated the example prints some statistics that show how much time each thread spends waiting for the mutex to become available.

The example can be built with the STD_MUTEX preprocessor symbol defined, in which case it will use a standard QMutex to synchronize file access. To do this, run qmake DEFINES+=STD_MUTEX to generate the makefile and rebuild the example.

    #include <qapplication.h>
    #include <qdatetime.h>
    #include <qdeepcopy.h>
    #include <qfile.h>
    #include <qtextedit.h>
    #include <qthread.h>
    #include <qtimer.h>

    #include "qtreadwritemutex.h"

    //#define STD_MUTEX

    #define NumReaders 30

    #ifdef STD_MUTEX
    #include <qmutex.h>
    QMutex mutex;
    #else
    QtReadWriteMutex mutex(NumReaders);
    #endif

    class LogEvent : public QCustomEvent
    {
    public:
        enum LogType {
            WriteRequest = QEvent::User + 1,
            WriteBegin,
            WriteEnd,
            ReadLog
        };

        LogEvent(LogType type, const QString &log = QString())
        : QCustomEvent(type), text(log)
        {
        }

        QString logText() { return text; }
    private:
        QDeepCopy<QString> text;
    };

    class Log : public QTextEdit
    {
    public:
        Log()
        {
            setTextFormat(Qt::LogText);
        }

    protected:
        void customEvent(QCustomEvent *e)
        {
            QString logstring(QDateTime::currentDateTime().toString() + ": ");

            LogEvent *le = (LogEvent*)e;
            switch (e->type()) {
            case LogEvent::WriteRequest:
                logstring += "Write access requested";
                break;
            case LogEvent::WriteBegin:
                logstring += "Write access begins";
                break;
            case LogEvent::WriteEnd:
                logstring += "Write access finished";
                break;
            case LogEvent::ReadLog:
                logstring += le->logText() + " read from file";
                break;
            default:
                return;
            }

            append(logstring);
        }
    };

    Log *logger = 0;

A custom event is used to send string-messages to the GUI thread. The Log class subclasses QTextEdit to handle the event. Note the use of QDeepCopy in the event class.

    class ReadThread : public QThread
    {
    public:
        ReadThread(const QString &filename, int number);
        ~ReadThread();

        void stop();

    protected:
        void run();

    private:
        QString logfile;
        int id;
        bool cancel;

        double percentblock;
        int accesses;
    };
    class WriteThread : public QThread
    {
    public:
        WriteThread(const QString &filename);
        ~WriteThread();

        void stop();

    protected:
        void run();

    private:
        QString logfile;
        bool cancel;

        double percentblock;
        int accesses;
    };

The ReadThread and WriteThread classes implement a QThread. The implementation of the QThread::run() functions is what is interesting.

    void ReadThread::run()
    {
        int totaltime = 0;
        int blocktime = 0;
        while (!cancel) {
            msleep(100 + 20 * id); // take a break

            QTime timer;
            timer.start();

The thread initiliazes some local variables, and then perform some work in a while loop that runs until the thread is stopped from the outside. The time spent in the thread is stopped using QTime.

    #ifdef STD_MUTEX
            QMutexLocker locker(&mutex);
    #else
            QtReadWriteMutexLocker locker(&mutex, QtReadWriteMutex::ReadAccess);
    #endif
            blocktime += timer.elapsed();
A mutex locker is used to lock and unlock the mutex that is used to synchronize access to the file. Depending on how the example was configured this is either a standard QMutex, or a QtReadWriteMutexLocker using ReadAccess. The time spent waiting for the mutex is stopped.

            if (cancel) {
                totaltime += timer.elapsed();
                break;
            }

            QFile file(logfile);
            if (!file.open(IO_ReadOnly))
                continue;

            ++accesses;
            QTextStream in(&file);

            QString string = in.readLine();
            string += QString(" read by %1").arg(id);
            // take a bit...
            msleep(100);
            QApplication::postEvent(logger, new LogEvent(LogEvent::ReadLog, string));
            totaltime += timer.elapsed();
        }

        percentblock = double(blocktime)/double(totaltime) * 100;
    }

If the thread has been stopped while waiting for the mutex the operation is canceled. Otherwise a file is opened for read access, the contents are read into a QString that is then posted to the log.

    WriteThread::WriteThread(const QString &filename)
    : logfile(filename), cancel(false), accesses(0)
    {
    }
Before the thread terminates the percentage of time spent waiting for the mutex is calculated.

    void WriteThread::run()
    {
        int totaltime = 0;
        int blocktime = 0;

        int writecount = 0;
        while (!cancel) {
            msleep(2500); // take a (longer) break

            QApplication::postEvent(logger, new LogEvent(LogEvent::WriteRequest));
            QTime timer;
            timer.start();
    #ifdef STD_MUTEX
            QMutexLocker locker(&mutex);
    #else
            QtReadWriteMutexLocker locker(&mutex, QtReadWriteMutex::WriteAccess);
    #endif
            blocktime += timer.elapsed();
            if (cancel) {
                totaltime += timer.elapsed();
                break;
            }
            QApplication::postEvent(logger, new LogEvent(LogEvent::WriteBegin));

            QFile file(logfile);
            file.open(IO_WriteOnly);
            ++accesses;
            QTextStream out(&file);

            // take some time getting data...
            msleep(100);
            QString string = QString("some data: %1").arg(++writecount);
            out << string << endl;
            QApplication::postEvent(logger, new LogEvent(LogEvent::WriteEnd));
            totaltime += timer.elapsed();
        }

        percentblock = double(blocktime)/double(totaltime) * 100;
    }

The writer-thread uses QtReadWriteMutexLocker to request write-access to the file, and posts events to the log indicating the state of the write operation. It does this every 2.5 seconds.

    int main(int argc, char **argv)
    {
        QApplication app(argc, argv);

        QString filename = argc > 2 ? argv[1] : "logfile.txt";
        QFile::remove(filename);

        logger = new Log();

        // High-priority writer thread
        WriteThread writer(filename);
        writer.start(QThread::HighPriority);

        int i;
        ReadThread *readers[NumReaders];
        for (i = 0; i < NumReaders; ++i) {
            readers[i] = new ReadThread(filename, i);
            readers[i]->start();
        }

        logger->show();

        app.setMainWidget(logger);

         // quit after 10 seconds
        QTimer::singleShot(10000, &app, SLOT(quit()));
        int result = app.exec();

        // first stop all threads
        writer.stop();
        for (i = 0; i < NumReaders; ++i)
            readers[i]->stop();

        // then wait for all threads to finishe
        writer.wait();
        for (i = 0; i < NumReaders; ++i) {
            readers[i]->wait();
            delete readers[i];
        }

        delete logger;
        return result;
    }

The main() function creates the user interface, creates and starts one WriteThread (with high priority) and multiple ReadThreads, and runs the application for 10 seconds. Then all threads are stopped and deleted.

Running this example with STD_MUTEX defined and with different numbers of reader threads shows that all threads spend a high percentage of their total working time waiting for the mutex to become available. Even with only three reader threads almost 50% of the time is spent waiting for the mutex; with 6 or more readers this grows quickly to 75% or more percent. The writer almost never has a chance to perform the 4 write operations expected (10 seconds, with a write access every 2.5 seconds)

Using the QtReadWriteMutex instead shows that the performance is already improved for very few threads - the readers spend almost no time waiting for the mutex, and the writer thread hardly ever has to wait more than 50% of the total time even with a large number of readers. Even then it performs the 4 write operations with a high probability.

Copyright © 2003-2004 TrolltechTrademarks
Qt Solutions