Introduction

Thread support was recently added to Qt 2.

What does this mean, exactly ?

In the bad old days, if you wanted to use threads within an app linked to Qt, you had to use another library for support.

There were some options open:

  • Use e.g. pthreads and provide some logic in your makefiles to make them work properly. On supported platforms only, of course.
  • Use different native thread APIs, and use #ifdefs in your code, plus even more makefile logic, to make them work properly.
  • Use a thread abstraction library and again some makefile logic.

One disadvantage of any of these approaches is that you are 'stepping outside' of Qt. A major advantage of Qt is that for many applications you can avoid all system-dependent code and even most system-dependent make rules (tmake helps here.)

Using a thread abstraction library (e.g. ZThread, QpThread (not related), etc) is a good interim solution, but you still need to work out how to link to it on all systems you support. You need to make sure that the library is available for each target platform. You might have to package the library with your application. The licensing of the library may not be suitable for your purposes or be compatible with the Qt license your are using.

After avoiding system-dependent code by using a thread abstraction library, ensuring the library is available on your target platforms, and verifying that the license suits your needs, theres still another hurdle.

Why write MT code ? (and why not, too)

Why would I want to write threaded code ? My app works just fine with an event loop, select(), etc.

Good question. Some people seem to think that threads are some kind of new fad, great for buzzword compliance (ref: CORBA) but not providing significant benefits.

Points:

  • Writing threaded programs is in some ways simpler than writing programs that simulate threaded behaviour.

    Writing threaded programs takes learning and practice, but the work involved in becoming proficient is not prohibitive and pays dividends in terms of clean design and therefore maintainability.

    Writing threaded programs also makes it harder to understand timing issues and communication within your process.

    If your libraries aren’t up to handling threaded code (non-thread-safe containers are an prime example) then you might just cause more problems than you fix.

  • Threaded programs are sometimes more efficient than their non-threaded counterparts.

    Thread support adds overhead (context switching,) but it reduces busy wait. Depending on your application, there may be no real problem with busy wait. A web server is a good example. The fastest web servers in the world use (at least partly) a select() based model.

    Sometimes it is simply impossible to make a program work correctly without taking advantage of the multitasking feature of your kernel.

    The traditional solution is to use separate processes. This is fine when there is no reason to share process space. aRts, for example, runs artsd as a standalone process. This is the logical design. artsd may use threads internally, for example if it wants to do CPU-intensive decoding of compressed audio without causing stalls in other parts of the program, for example piping decoded audio to an output device, or applying filters to audio data.

  • Some languages have had thread support for years (Java had it from birth.)

    Threading is not some new fad that will go away when people realise it’s just a buzzword. Resistance to using threads within C++ is analogous to resistance to move from C to C++. Moving from C to C++ can help you write better software. C++ helps you write more efficient programs than those you might write in C. Using threads can do the same.

    N.B. C++ programs _can_ be more efficient than C programs. This is not because the compiled form of code runs any faster. C++ programs incur slight overhead due to such delights as virtual tables. The reason that C++ programs can run faster is because of the way that OO programmers design systems. Because it’s easier to implement a system, it’s easier to make it efficient. If you’re looking for a comfortable walk in the hills, you can save energy by reading a map, noting contour lines and distances between points. Just walking in the right direction will get you there, but learning map-reading skills is the intelligent thing to do.

An important point emerges: Using threads is not some magic bullet to make your programs better, in the same way as moving from C to C++ is not. A bad programmer is a bad programmer, whatever language or tools they use.

If your threaded program doesn’t give you what you expect, either your expectations are wrong or your design is wrong. I reiterate: threads are not a magic bullet. Learning how to use threads effectively, identifying where to use threads and, more importantly, where _not_ to use them, are new skill you must learn.

The state of Qt thread support

Qt uses an event loop to simulate non-blocking behaviour. In a multi- threaded application, the event loop could be accessed by more than one thread at a time. This could have disastrous results - and usually does.

To avoid trouble, it is necessary to lock the event loop. Locking cannot be done properly if Qt itself has no thread support. The only major difference between Qt and Qt-MT at the moment (apart from some extra classes) is that a mutex protects the event loop.

Qt is still not thread safe. To make it thread safe, all classes need to be checked for safety and adjusted accordingly. This is a huge undertaking. If it does happen, I would guess it’s a long way away.

Converting all classes to be thread-safe is not as simple as just sticking a mutex on its data. Qt uses explicit and implicit shared classes for (memory) efficiency. Especially implicit sharing does not work well with threads. It is _possible_ to make all of Qt thread-safe, but the necessary locking and unlocking of mutexes would cost some CPU time. The concept of thread-safety is not as simple as it sounds, either. If you want to read further discussion of thread safety and shared data within Qt, plus my ideas on possible solutions, please read qt-mt.html.

Before you start feeling gloomy, I should mention that the fact that Qt’s classes are not thread safe is not really a major problem. I have written a lot of code which uses Qt classes within threads and had no trouble. You simply have to learn what you can and can’t do.

KDE-specific issues

Issues related to using threads in KDE programs are similar to those raised when writing pure Qt programs.

KDE depends on Qt. That means that when Qt supports threads on a platform, KDE does. The necessary changes to KDE when another platform is supported by Qt are trivial. Some extra compiler and linker flags in makefiles are all that is necessary.

The KDE libraries are not thread safe (yet.) This is _not_ a problem, in the same way as it is not a problem that Qt is not thread safe. Exactly the same issues apply to writing KDE programs as apply to pure Qt programs.

Many KDE classes are GUI classes. These should be accessed from the GUI thread only. That’s pretty simple. The non-GUI (read: no access to event loop) classes can of course be used from within other threads, but you just need to use your brain. There are static functions, there are singleton classes. There is shared data. You need to think about what should be kept within a thread. While KDE libraries are not thread safe, this means practically everything.

As with Qt, taking care how you use KDE library classes is not as restrictive as it sounds. If you plan your abstraction well, your design will allow you to use only a small subset of Qt and KDE classes within worker threads. If you want to use KGlobal::config(), use it outside the thread. Threads are for doing things like CPU and IO, not for reading an application’s config. Use them for their intended purpose.

A further complication with using threads in KDE is that there is no official support yet. If you write an application for KDE that uses Qt threads, you’re basically on your own for the moment. KDE does not link to the MT version of the Qt library yet.

You can set a flag when configuring KDE, which will make KDE link to the MT version of the Qt library, but as you’ll see, it’s marked experimental. There are good reasons for this:

  1. Linking with libqt-mt is not binary compatible on any systems but Linux. We don’t want to break binary compatibility before KDE 3.0.

    N.B. BC will be broken before KDE 3.0 due to reasons beyond our control. This works in favour of thread support testing, because we may be able to advance testing further than would otherwise have been possible.

  2. Qt currently has thread support only for Linux, FreeBSD and Solaris.

  3. Due to 1. and 2., KDE will not be linked with libqt-mt by default until KDE 3.0.

    If Qt does not support threads on all platforms that KDE supports by the time KDE 3.0 is close to release, then KDE 3.0 will not link to libqt-mt, either.

If proper support is so far away, why is the optional flag there already ? The answer is simple. The earlier we begin testing support, the more stable it will be when we reach release.

Should you start using threads in your KDE application ?

The simple answer to this is no. Because KDE is not linked to libqt-mt, you will find it diicult to distribute your application. KDE users will not have KDE linked to libqt-mt. You can’t relink it for them.

If you want to release a KDE application that links to libqt-mt, your best bet is to link statically with the Qt and KDE libraries. Yes, you’d have to be desperate.

Commercial issues

The sooner we get KDE linked to libqt-mt by default, the sooner commercial vendors (e.g. IBM, Borland) can start writing threaded KDE applications. Vendors can cheat now if they want, by employing one of the methods discussed earlier. The stated disadvantages apply, but it’s not impossible.

If commercial vendors (and non-commercial, for that matter,) can already cheat and use separate threading packages or native support, why is it so important that KDE has thread support soon ?

Some examples:

  • Borland’s Kylix. This will be a great developer tool, and will provide excellent support for writing Qt-based applications. The problem for Borland is that they cannot provide support for writing threaded applications for KDE, even though Qt already has support on some major platforms.
  • IBM’s ViaVoice. Speech synthesis and recognition is highly CPU intensive. If IBM want to use threads to do any of this work, then they could use Qt threads, which would greatly simplify their task. As things stand, they can’t really do that, because KDE doesn’t link to the MT version of Qt.

Both of these examples may in fact be non-issues, but I hope you see why I consider KDE support for Qt-MT to be important. KDE needs killer apps, and people who write killer apps are not going to be pleased when they find that features they want to use are unavailable.

Tips for writing MT code

Here are a couple of rules of thumb that I’ve figured out while playing with threaded Qt:

  • Keep the event loop in one thread. Don’t try to play with it from other threads. What this means is, don’t use QObject-derived classes in threads other than the GUI thread. Events, signals, slots etc are not going to work.
  • Don’t pass shared classes between threads unless you have protected them with mutexes. For example, you really don’t want to write to a QString from your GUI thread and then expect a worker thread to read it safely.

Using threads requires having knowledge of the concepts and adjusting your programming style accordingly. Yes, you have to think in a different way. Threaded code can have a simpler design than non-threaded, but entropy being what it is, you have to use more brain cells when doing your design.

Debugging

A quick note on debugging threaded apps. If you’re using Linux and gdb version 5.x, there should be no problem. Attaching to a running process doesn’t work, but running the app from within the debugger works just fine. I can’t comment on other systems. If anyone has info, please let me know so I can add it here.

Examples

Example one

Introduction

My first example will be a music player. I chose this because the first threaded code I ever wrote was a music player. I wrote it as a four- hour hack as soon as Qt gained thread support. By doing this I taught myself about the issues involved with threading,.sourceluding Qt-specific issues. I hope you find this useful. I will add a second example if enough people request it. If you feel confident enough to delve into source, you could just read the code in kdepim/empath/lib/ and see how the 'job' mechanism works, but be warned, it’s large and not obvious on first reading, especially if you’re not familiar with the design.

Music player - design brief

Write a Qt application which can decode Ogg Vorbis compressed audio and play to an output device. The user interface should not stall while the application is busy (e.g. decoding or writing audio data) and it should work smoothly, for example there should be no audible gap between audio tracks.

Approaching this as an OO developer, the usual start to the design process is to simply pick an approach which seems logical. Of course, the first ideas are not completely random, rather drawn from experience and intuition.

On first inspection, it seems logical to separate the system into three separate modules, the input, the output and the user interface. In C++ terms, this translates to creating three classes, Input, Output and Interface.

Information flow

  • There should be some buffering of audio data between decoding and output. If the program is unable to get enough processor time at some point, which is inevitable on a multitasking system, then the sound device’s (usually) small buffer may become empty while the application is trying to decode more audio data. Having a buffer of ready-decoded data should alleviate the problem.
  • Interface must have control over Input and Output. It should be able to tell Input what to decode, and tell Output to start or stop streaming data.
  • Input must be able to hand decoded data to Output. The data must be buffered. Doing the buffering between Input and Output seems reasonable. Input must also be able to provide extra information about this data, such as the name of the current track, the song position, etc.
  • Output must be able to relay track information to Interface. It seems logical to have information sent from Output rather than Input, because the information will be better synchronised with the actual sound output if it is sent around the time the data is streamed to the audio device.

The main implementation issues

  • How will Interface communicate with Input and Output ?
  • How will the data from Input be sent to Output and how will it be buffered ?
  • How will the communication between Output and Interface be realised ?

Again, choosing method by intuition:

  • Input and Output need to be free to continually process data. The way that the Vorbis decoder and the sound device are implemented means that data processing is done in reasonable small blocks, with expected time to process a block significantly less than 10ms. This gives plenty of time to process commands while looping and block processing. Commands should be processed one at a time, so the appropriate data structure is a FIFO (queue.)
  • The appropriate data structure is again a FIFO. This could be placed between Input and Output, with access policed by locking and size limits enforced to avoid decoding all data into memory at once (decoding must take less time than output, otherwise the player doesn’t work anyway :)
  • The user interface is event driven. Not surprisingly, events are held in a FIFO. We need a way to get events into this queue. This is where we see a great feature of QThread, the postEvent() method. Using QThread::postEvent(), it’s possible to place events on the queue, while the thread is running.

    Any QObject can receive events, so all we need to do is to tell Output which QObject it should send events to with postEvent(). The events it sends will be ‘user defined.’ Qt does not support 'end of track; events !

    Qt allows sending user defined events by giving them ids >=QEvent::User, plus you may inherit from QEvent to store data within your custom events.

Code sketches

There now follows a sketch of the more important classes from the application. These have been copied by hand from the working sources then adjusted for simplicity and clarity, so I might have introduced a bug or two. Please take these as illustration of method, not as sample implementation. The source code for the actual application is available to those who want it, just ask me.

Because we want to share a queue between two threads, we need to make sure that we have a thread safe version. Here’s my implementation:


template<class T> class MTQueue
{
  public:

  MTQueue(uint maxSize)
    : maxSize_(maxSize)
  {
  }

  ~MTQueue()
  {
    flush();
  }

  bool isFull()
  {
    mutex_.lock();
    bool full = queue_.count() >= maxSize_;
    mutex_.unlock();
    return full;
  }

  bool isEmpty()
  {
    mutex_.lock();
    bool empty = queue_.isEmpty();
    mutex_.unlock();
    return empty;
  }

  void flush()
  {
    mutex_.lock();
    while (!queue_.isEmpty())
      delete (queue_.dequeue());
    mutex_.unlock();
  }

  void enqueue(T * t)
  {
    mutex_.lock();
    queue_.enqueue(t);
    mutex_.unlock();
  }

  T * dequeue()
  {
    mutex_.lock();
    T * i = queue_.dequeue();
    mutex_.unlock();
    return i;
  }

  private:

    int maxSize_;
    QQueue<T> queue_;
    QMutex mutex_;
};

We want to be able to pass blocks of data around, and we also want to be able to store extra information with the audio data, so we define a class to store this information.


class BufferItem
{
  public:

  BufferItem();

  BufferItem(
      QByteArray  buffer,
      uint        time,
      uint        trackLength,
      uint        trackNumber
  )
    : buffer_       (buffer),
      time_         (time),
      trackLength_  (trackLength),
      trackNumber_  (trackNumber)
  {
  }

  QByteArray  buffer()        const { return buffer_;       }
  uint        time()          const { return time_;         }
  uint        trackLength()   const { return trackLength_;  }
  uint        trackNumber()   const { return trackNumber_;  }

  private:

  QByteArray  buffer_;
  uint time_;
  uint trackLength_;
  uint trackNumber_;
};

Sample implementation of Input:


class Input : public QThread
{
  public:

  class Command {

    public:

      enum Type { Play, Stop, Next, Previous, Pause, Skip };

      Command(Type type, int data = -1)
        : type_(type),
          data_(data)
      {
      }

      Type type() const { return type_; }
      int data() const { return data_; }

    private:

      Type type_;
      int data_;
  };

  Input(QObject * parent, MTQueue<BufferItem> * queue)
    : QThread (),
      parent_ (parent),
      queue_  (queue)
  {
  }

  void play()       { commandQueue_.enqueue(new Command(Command::Play));     }
  void stop()       { commandQueue_.enqueue(new Command(Command::Stop));     }
  void next()       { commandQueue_.enqueue(new Command(Command::Next));     }
  void previous()   { commandQueue_.enqueue(new Command(Command::Previous)); }
  void pause()      { commandQueue_.enqueue(new Command(Command::Pause));    }
  void skip(uint t) { commandQueue_.enqueue(new Command(Command::Skip, t));  }

  protected:

  void run()
  {
    while (true)
    {
      if (!commandQueue_.isEmpty())
      {
        Command * c = commandQueue_.dequeue();

        switch (c->type())
        {
          case Command::Play:
            _changeTrack(0);
            break;

          case Command::Next:
            _changeTrack(currentTrack_ + 1);
            break;

          case Command::Previous:
            _changeTrack(currentTrack_ - 1);
            break;

          case Command::Stop:
            _changeTrack(-1);
            break;

          case Command::Skip:
            _changeTrack(c->data());
            break;
        }
      }

      if (!read()) // We ran out of data. Go to next track.
        _changeTrack(currentTrack_ + 1);
  }

  void read()
  {
    QByteArray input(4096);

    ...;                      // Decode some data.
    uint time = ...;          // Find out from decoder.
    uint trackNumber = ...;   // Find out from decoder.
    uint trackLength = ...;   // Find out from decoder.

    while (queue_->isFull())
      msleep(1);

    queue_->enqueue(new BufferItem(input, time, trackNumber, trackLength));
  }

  private:

  QObject * parent_;
  MTQueue<BufferItem> * queue_;
  MTQueue<Command> commandQueue_;
};

Sample implementation of Output:


class Output : public QThread
{
  public:

  enum Status { Init, NoData, Playing, Paused };

  Output(QObject * parent)
    : QThread       (),
      parent_       (parent),
      queue_        (queue),
      pause_        (false),
      flush_        (false),
      status_       (Init),
      time_         (0),
      trackNumber_  (0),
      trackLength_  (0)

  {
  }

  ~Output()
  {
    flush_ = true;

    wait(10);

    if (_isOpen())
      close();
  }

  uint time() const   { return time_;   }
  void flush()        { flush_ = true;  }
  void pause(bool b)  { pause_ = b;     }

  protected:

  void run()
  {

    while (true)
    {

      if (pause_)
      {
        if (status_ != Paused)
        {
          status_ = Paused;
          postEvent(parent, new StatusChangeEvent(Paused));
        }

        msleep(1);
        continue;
      }

      if (flush_)
      {
        flush_ = false;
        queue_->flush();
      }

      if (queue_->isEmpty())
      {
        if (status_ != NoData)
        {
          status_ = NoData;
          postEvent(parent_, new StatusChangeEvent(NoData));
        }

        msleep(1);
        continue;
      }

      if (status != Playing)
      {
        status_ = Playing;
        postEvent(parent_, new StatusChangeEvent(Playing));
      }

      BufferItem * i = queue->dequeue();

      int newTime     = i->time();
      int newNumber   = i->trackNumber();
      int newLength   = i->trackLength();

      _write(i);

      delete i;
      i = 0;

      if (newTime != time_)
        postEvent(parent, new TimeChangeEvent(newTime));

      if (newNumber != trackNumber_)
        postEvent(parent, new TrackChangeEvent(newNumber));

      if (newLength != trackLength_)
        postEvent(parent_, new TrackLengthChangeEvent(newLength));

      time_         = newTime;
      trackNumber_  = newNumber;
      trackLength_  = newLength;
  }

  virtual bool _init()
  {
    ...;    // Open audio device.

    status_ = NoData;
  }

  virtual void _write(BufferItem *)
  {
    ...;    // Write to audio device.
  }

  virtual bool _isOpen()
  {
    ...;    // Find out if audio device is open.
  }

  virtual void _close()
  {
    ...;    // Close audio device.
  }

  private:

  QObject             * parent_;
  MTQueue<BufferItem> * queue_;

  bool                  pause_;
  bool                  flush_;
  OutputStatus          status_;
  uint                  time_;
  uint                  trackNumber_;
  uint                  trackLength_;
};

Example two

My second example is a CD player. It should be very portable as it uses only Qt and the libcdaudio support library, available here (note: link seems broken).

I won’t discuss the source here, as I believe I have made it quite readable. You will probably find bugs in the code, but I hope they won’t be related to the threading. The app itself is not supposed to be the best CD player in the world, it’s supposed to demonstrate how Qt’s thread support can be used safely and effectively, giving non-blocking behaviour whilst providing a usable API.

The design is probably best described as MVC, with a small extension to the concept to allow converting events created by the CDPlayer object to signals and a similar system for connecting to the non-existant CDPlayer slots. The two proxy classes (CDPlayerView and CDPlayerController) perform these tasks, which are not strictly necessary, but make the threaded class fit better in the Qt object model.

I believe the design to be sensible, safe and easy to understand, but you may disagree. Please let me know if you do, because I would rather admit my design is bogus than mislead other developers.

Source code

cdplayer.tar.gz

If anyone would like to fix any bugs left in the code and add CDDB support, perhaps we could use it for kscd in KDE 3. Except the 'functional; UI of course ;)