QThread QMutex QSemaphore and Multi Threaded Applications

Ever wondered what a Thread Mutex is? How about a Semaphore? You know they deal with threads but you probably never understood how they work. This article will examine QThread, QMutex, QSemaphore in a multi-threaded application and explain it using Qt (though you don't need to know Qt to learn this important programming concept), it's actually easier than learning it using Win32 Programming.

Mutex Explained

Basically, a Mutex locks itself, and does not get locked again until it is unlocked first.

If we have an integer called X (int X = 5), and multiple threads running at the same time and they all want to write to X, well this can definitely crash.

Thus, in each thread we lock the SAME Mutex variable. So Thread1 will lock the Mutex first. Then Thread2 will try to lock it, but it cannot, because Thread1 is not finished with variable X and has not unlocked Mutex. Hence, Thread2 and Thread3 will wait until Mutex is unlocked.

Think of it as a locker.

Semaphore Explained

A Semaphore is an integer in its basic form, and it can start from any number, and many different threads will pull 1 number from it. If we start the Semaphore at 5, the threads will all start doing their work as long as there is 5 in the Semaphore, but each thread takes 1.

Thus, we can have up to 5 threads running, until we run out of the Semaphore.

Think of it as movie tickets for threads. It can only have so many movie tickets in order to watch a movie, it's like a mutex but instead of protecting a buffer, it protects amounts of resources.

QThread Using Both Semaphore and Mutex to Write In Buffer

In this example, similar to the one at the Qt documentation, the program will write into the buffer, until we have reached an array limit of 5!

int buffer[5]; is our main buffer.

Many producer threads will be made to create random numbers to fill buffer[] array.

And at the same time, consumer threads will be made to delete items from buffer[] array.

Producer threads must be made so it can only fill up to the 5th index of the array.

Consumer threads must not delete something that doesn't exist.

#include <QtGui>
#include <iostream>

using namespace std;

/* Initialize the buffer  */
#define MAX_BUFFER_SIZE 5
int buffer[MAX_BUFFER_SIZE];

/* Initialize the Semaphore */
QSemaphore Full(MAX_BUFFER_SIZE);
QSemaphore Empty;

/* Initialize the Mutex */
QMutex Mutex;

/* Insert a random item into the buffer */
int insert_item(int item){
    if(Full.tryAcquire()){  /* Acquire a Full Resource */
        Mutex.lock(); /* Lock the Mutex, writing to buffer */
        buffer[Full.available()] = item;
        Mutex.unlock(); /* Unlock the Mutex, finished writing to buffer */
        Empty.release();  /* Create new empty resource */
        return 0;
    } else { /* If cannot acquire more resources */
        return -1;
    }
}

int remove_item(int *item){
    if(Empty.tryAcquire()){ /* Acquire an Empty Resource */
        Mutex.lock();
        *item = buffer[Full.available()];
        buffer[Full.available()] = 0;
        Mutex.unlock();
        Full.release(); /* Create new full resource */
        return 0;
    } else {
        return -1;
    }
}

class Consumer : public QThread {
    public:
        Consumer(){ msleep(1); }
        void run();
};

void Consumer::run(){
    for(;;){
        int item;
        sleep(5);  /* Sleep 5 seconds */
        if(remove_item(&item) == 0){
            cout << "Consumer has read " << item << " from the buffer.\n";
        } else {  /* Check for error */
            cout << "Buffer is empty.\n";
        }
     }
}

class Producer : public QThread {
    public:
        Producer(){ msleep(5); }
        void run();
};

void Producer::run(){
    for(;;){ // infinite array
        sleep(5);
        qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()));  /* Seed random time */
        int iRand = (int)qrand()%200;  /* Acquire Random Time */
        if(insert_item(iRand) == 0){
            cout << "Producer has written " << iRand << " into the buffer.\n";
        } else {
            cout << "Buffer is full.\n";
        }
    }
}

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
      /* Create 5 producers */
    Producer p1;
    Producer p2;
    Producer p3;
    Producer p4;
    Producer p5;
    Consumer c1;
    Consumer c2;
    p1.start();
    p2.start();
    p3.start();
    p4.start();
    p5.start();
    c1.start();
    c2.start();
     /* Wait for threads to be over (Though they will never be over) */
    p1.wait();
    p2.wait();
    p3.wait();
    p4.wait();
    p5.wait();
    c1.wait();
    c2.wait();
    return app.exec();
}

Semaphore Empty starts from 0, and Semaphore Full starts from 5.

Producers begin to create threads (and random numbers) to fill buffer, and they stop when they have "acquired" all 5 numbers from Semaphore Full! Meanwhile they will "release" (create) new Empty resources.

Consumers begin to create threads (to delete those random numbers) to empty buffer, and they stop when they have "acquired" all the numbers from Semaphore Empty. Meanwhile they will "release" (create) new Full resources.

If you are compiling with Win32 Qt use:
win32 {
CONFIG += console
}

In your Qt pro file. In Linux you should be using terminal anyway.

The output looks something like this:

Producer has written 124 into the buffer.
Producer has written 77 into the buffer.
Producer has written 32 into the buffer.
Producer has written 12 into the buffer.
Producer has written 95 into the buffer.
Consumer has read 95 from the buffer.
Consumer has read 12 from the buffer.
Producer has written 44 into the buffer.
Producer has written 67 into the buffer.
Buffer is full.
Buffer is full.
Buffer is full.
Consumer has read 67 from the buffer.
Consumer has read 44 from the buffer.

As the results show, Producers cannot produce more than 5, and the Consumers cannot consume more than 5 (or it will say buffer is empty).

toglia3d's picture

Why do you have to exec a

Why do you have to exec a QApplication? Is this necessary?

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.