Lock Free Multithreading in Qt

If multithreading is challenging to get right in your applications, then lock-free multithreading is down-right killer.

This article won’t go into detail about lock-free algorithms, but instead I will offer a “poor man’s” method for crossing thread boundaries in Qt without using locks (no mutexes, no semaphores). At least, your code won’t have any locks. More on that later.

For years, Qt has sported an easy-to-use threading library, based around a class called QThread. As of Qt4, you can use QThread to start your own event loops. This might sound somewhat uninteresting at first, but it means you can have your own signals and slots outside the main thread. The Trolls created a new way to connect signals to slots such that signals can actually cross thread boundaries. I can now emit a signal in one thread and receive it in a slot in a different thread. This is hugely useful when you want to, for example, integrate a blocking-happy library into your application. Here’s what I’m talking about in pictures:

Signals can arrive at any time from the threads, just like any other signal, and the code in the main event loop doesn’t know anything about multi-threading, locks, or condition variables.

An Example

Let’s say you want to integrate with a third-party library that blocks when you call its functions. Of course, if you call this library from your main event loop, your application will freeze while it’s running. That would be annoying, and you can give your users a better experience than that. Let’s wrap that library in a QThread. First, you need to declare a new QThread subclass, like this:

class MyLibraryWrapper : public QThread
{
Q_OBJECT
public:
    MyLibraryWrapper();
protected:
   void run();
signals:
   void done(const QString &results);
private slots:
   void doTheWork();
};

The run() method is called automagically by Qt when the caller calls start() on your thread. This is similar to how Java’s Thread class works.

The done() signal is how the object tells you it is finished with its work. It emits a QString in this example, but it can emit anything you want (note that if you want to emit non-primitive or non-Qt types, you need to use the Q_DECLARE_METATYPE() macro, which we won’t go into in this article).

The doTheWork() slot is there to actually do the blocking work. It has to be a slot so we can put it to work after our event loop starts up (which you’ll see in a minute).

Now for the implementation of MyLibraryWrapper:

MyLibraryWrapper::MyLibraryWrapper() : QThread()
{
  // We have to do this to make sure our thread has the
  // correct affinity.
  moveToThread(this);

  // This will do nothing until the user calls start().
}

void MyLibraryWrapper::run()
{
  // This schedules the doTheWork() function
  // to run just after our event loop starts up
  QTimer::singleShot(0, this, SLOT(doTheWork()));

  // This starts the event loop. Note that
  // exec() does not return until the
  // event loop is stopped.
  exec();    
}

void MyLibraryWrapper::doTheWork()
{
  // Do the heavy-duty blocking stuff here
  // (simulated by a 5 second sleep for
  // this example)
  sleep(5);

  // When you're done, emit the results:
  emit done("First job's finished.");
  
  // And some more sleeping for fun
  sleep(3)
  emit done("Second job's finished.");

  // ...
}

To actually use this new class, all you have to do is instantiate it in your main event loop and connect it to a slot. Here’s an example. Let’s say you have a QMainWindow called MyMainWindow. This is how you would set it up as a result of a user button click:

void MyMainWindow::on_someButton_clicked()
{
  MyLibraryWrapper *wrapper = new MyLibraryWrapper();

  // This is the magic that tells the wrapper to
  // notify us when it's done. We use a QueuedConnection
  // to make sure Qt delivers the signal in a thread
  // safe manner
  connect(wrapper, SIGNAL(done(QString)),
          this, SLOT(wrapperDone(QString)),
          Qt::QueuedConnection);

  // This kicks off the wrapper's event loop by causing its
  // run() method to be called
  wrapper->start();
}

void MyMainWindow::wrapperDone(const QString &results)
{
  // The wrapper is now done with its long, blocking
  // operation, and we didn't freeze the application.
  // Yay for us!

  qDebug() << "Here are your results:" << results;
}

Conclusion

Notice that none of our code uses a semaphore, condition variable, or mutex? Using QThread makes it super easy to wrap up libraries that need to block the event loop in a way that is transparent to the caller.

I claimed earlier that this code would be “lock free”. I can’t actually claim that is 100% true. I don’t know for sure, but I imagine that Qt’s internal event loop code does indeed use locks to pass signals between event loops. All I know for sure is that this code crosses thread boundaries without any locks.

Stay tuned for another article that shows how you can call setter functions on your threaded objects without any need for locks.

Write a Reply or Comment

Your email address will not be published.