[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-devel
Subject:    Multithreading with Pth HOWTO (fixed)
From:       Roberto Alsina <ralsina () unl ! edu ! ar>
Date:       2000-03-07 8:27:37
[Download RAW message or body]



Fixed some silly mistakes in the example code, I apologize, but I made the
examples by copy/paste from my code :-)

I have been asked to write a working example, I will post it somewhere
today or tomorrow.

 ("\''/").__..-''"`-. .         Roberto Alsina
 `9_ 9  )   `-. (    ).`-._.`)  ralsina@unl.edu.ar
 (_Y_.)' ._   ) `._`.  " -.-'   Centro de Telematica
  _..`-'_..-_/ /-'_.'           Universidad Nacional del Litoral
(l)-'' ((i).' ((!.'             Santa Fe - Argentina
                                KDE Developer (MFCH)
The stone age didn't end for a lack of stone" Firoz Rasul

How to make a threaded app with Qt/KDE
--------------------------------------

Introduction
------------

	The purpose of this document is to explain the pros and cons of
implementing a multithreaded KDE (or Qt) application, as well as
explaining briefly the way in which that can be done.

Why multithreaded apps are good
-------------------------------

	Multithreaded apps are good in that they allow you to have a
non-blocking GUI without doing much trickery. For instance, when you
want to perform a lengthy calculation in a singlethreaded Qt
application, you need to put some processEvents() calls in between
lengthy operations like this:

void doLongThing()
{
    bool a;
    for (;a;)
    {
        a=doSomething();
        qApp->processEvents();
    }
}

This has a serious downside: while you will have a UI that redraws itself
correctly (as long as doSomething() doesn't take long), you STILL need to 
block (or rather, disable) most of your UI.

If you don't disable part of the UI, qApp->processEvents() will get a
button press and send your app to do something else entirely making your
life difficult, or you have to write your app to be able to juggle
separate calls to doLongThing() at the same time, for example.

There is an alternative, which is moving lengthy operations into separate
processes, and just have your app wait for the separate processes to end.
This is the approach used in KDE for KIO. This is good! This is hard to do
well, just ask the guys who designed KIO ;-)

On a multithreaded app, you would not do a qApp->processEvents() and
simply loop at will. You won't disable the UI because there is no a priori
reason why you can't do several doLongThing() at the same time, just make
sure each one runs in its own thread.

Why multithreaded apps are bad
------------------------------

	Multithreaded apps are bad in several ways. For one thing, there is no
way to implement a really multithreaded app that uses Qt from several threads
at once, because Qt is not reentrant.


	What's being reentrant? Let me quote Ralf S. Engelschall that knows
much more than I do ;-)

==============
     o reentrant, thread-safe and asynchronous-safe functions
       A reentrant function is one that behaves correctly if it
       is called simultaneously by several threads and then also
       executes simultaneously.  Functions that access global
       state, such as memory or files, of course, need to be
       carefully designed in order to be reentrant. Two
       traditional approaches to solve these problems are
       caller-supplied states and thread-specific data.

       Thread-safety is the avoidance of data races, i.e.,
       situations in which data is set to either correct or
       incorrect value depending upon the (unpredictable) order
       in which multiple threads access and modify the data. So a
       function is thread-safe when it still behaves semantically
       correct when called simultaneously by several threads (it
       is not required that the functions also execute
       simultaneously). The traditional approach to achieve
       thread-safety is to wrap a function body with an internal
       mutual exclusion lock (aka `mutex'). As you should
       recognize, reentrant is a stronger attribute than thread-
       safe, because it is harder to achieve and results
       especially in no run-time contention between threads. So,
       a reentrant function is always thread-safe, but not vice
       versa.
==============
        
        So, you CAN implement a multithreaded Qt application, as long as
you keep all your Qt (and KDE) related function calls in a single thread,
and move to other threads things like numerical calculations.

	Sadly, this is a real pain in a KDE application, because we do
LOTS of things using Qt and KDE libs. For instance, I use kfileio (part of
kedit, I believe) to load text files into memory, because it shows nice
warning boxes when something goes wrong. I can't do that from a separate
thread. That's not good.

	There is also a now minor problem: real threads (Pthreads) are not
all that portable. However these days most unix-like systems support them
in their latest versions, so it's not as bad as it used to be.

GNU Pth, and why its good
-------------------------

	Since real multithreaded apps are not all that possible, and good
non-multithreaded apps are not too easy to write in some situations, I looked
around, and found GNU Pth, the GNU Portable threads library.

	GNU Pth does cooperative userspace multithreading. What does that 
mean?

	Userspace means that it doesn't require kernel support. That helps
with the portability problem. GNU Pth works on systems like libc5 linux where
pthreads don't exist (or suck).

	Cooperative multithreading means that threads are not suspended to 
allow for the execution of another thread until they yield control, or suspend
implicitly by calling some functions that, well, suspend them while waiting
for some event (like I/O).

	To those here who are now thinking of Windows 3.1 and Macintosh's
cooperative multitasking, don't be too afraid, you only need to cooperate 
with yourself, so it's not that crappy :-)

	So, how would our code look using GNU Pth?
        
void doLongThing()
{
    bool a;
    for (;a;)
    {
        a=doSomething();
        pth_yield(NULL);
    }
}
        
	Not all that different :-)
        
        However, there is a big difference: running several doLongThing()s 
at the same time is rather natural, as long as you make sure each runs in
a separate thread, and you don't really need to disable the UI!

	Also, since using cooperative MT means that your code is not 
preempted, you CAN call Qt and KDELibs from the secondary threads, with some
very minor caveats (which we will see).

How to make a multithreaded QApplication
----------------------------------------

Here is the simple class I use to make a multithreaded QApp (modify in the 
obvious way to make it a KApp):

class QMTApplication: public QApplication
{
    Q_OBJECT
public:
    QMTApplication (int argc, char **argv);
    ~QMTApplication ();
    int exec();
    void quit();
    bool exitguithread;
}

QMTApplication::QMTApplication (int argc, char **argc): QApplication (argc,argv)
{
    exitguithread=false;
    //Initialize the Pth library
    pth_init();
}

QMTApplication::~QMTApplication ()
{
    //Kill the Pth library
    pth_kill();
}

int QMTApplication::exec()
{
    for (;!exitguithread;)
    {
        processEvents(); //Process any events
        pth_nap(pth_time(0,1000)); //wait 1/1000 of a second, so we don't hog
                                   //the CPU and let other threads execute.
    }
}

void QMTApplication::quit()
{
    exitguithread=true;
}

	What does it do? It simply initializes and closes the Pth library in 
the right moments, and yields processing to the other threads 1000 times
per second (more or less).

	Even if exec() seems to be a CPU hog, it is not. CPU usage is not
measurable on a idle application.

	Now, while this is technically a multithreaded application, it's
a one-thread MT app, which is not too interesting. What you need to do
to take advantage of Pth, is make your application spawn new threads whenever
you start doing something that takes long.

	So, suppose a button has a slot connected to its clicked() signal, and
that slot is called slotDoLongThing().

	Here is how that slot will look (but it can be done prettier, I bet).

//Prototype of the real function that does the work
void *slotDoLongThingThread(void *arg);

void MyButton::slotDoLongThing(void)
{
    //This defines some aspects of the child thread, look at the Pth
    //docs.
    pth_attr_t attr = pth_attr_new();
    pth_attr_set(attr,PTH_ATTR_CANCEL_STATE,PTH_CANCEL_ASYNCHRONOUS);
    pth_attr_set(attr,PTH_ATTR_JOINABLE,FALSE);
    pth_attr_set(attr, PTH_ATTR_STACK_SIZE, 128*1024);
    pth_spawn(attr,slotDoLongThing,this);
}

//The real function that does the work.
void *slotDoLongThingThread(void *arg)
{
    MyButton *myb=(MyButton *)arg;
    bool a;
    for (;a;)
    {
        a=doSomething();
        myb->doThingWithButton();
        pth_yield(NULL);
    }
    return NULL;
}

	So, whenever slotDoLongThing is called, it will spawn a new thread
to doSomething() a long while, and your UI will not block (because you call
pth_yield(), or at least, it will block as little as it would if you called
processEvents(), and you don't need to disable the UI because multiple
doLongThings can be done without any further programming trick.

	As a rule, I never call processEvents() from any of the spawned 
threads because it doesn't make sense: you get in the same mess as if you
were not using Pth! I just pth_yield(NULL), and eventually it will get to
a processEvents() in the main thread.

What using Pth won't get you
----------------------------

	You won't take advantage of multiple CPUs. You need real Pthreads 
for that.

	You don't get non-blocking I/O... unless you use pth_read instead
of read(), pth_write() instead of write() and so on. You may or may not
be able to take advantage of that, depending on your application.

        You don't get PERFECTLY non-blocking UI. You get a mostly non-blocking
one. If you don't call pth_yield(), you WILL block. In most cases, you get
a GOOD ENOUGH nonblocking UI, though.

	You don't get rid of data races. You get rid of MOST data races, but
you can get some. Consider what happens in the example if someone deletes the
button :-) You can fix most of these things by making objects abort threads
that handle references to them. here's what I use (with a bit of multiple
inheritance):

class ThreadOwner
{
public:
    ThreadOwner() {};
    ~ThreadOwner()
    {
        QListIterator <pth_t> it(threads);
        for (;it.current();++it)
        {
            pth_abort(*it.current());
        }
    };
    void registerThread(const pth_t *t)
    {
        threads.append(t);
    };
    void releaseThread(const pth_t *t)
    {
        threads.removeRef(t);
    };

    QList <pth_t> threads;
};

	Just make sure you call registerThread when you spawn a thread, 
releaseThread when the thread exits, and you are probably safe.

Final words:
------------

	While this is by no means a universal solution, I have found it useful.
I am sure there are lots of improvements better programmers than I can do, so
don't be shy to explain them to me :-)

	You can get GNU Pth from http://www.gnu.org/software/pth/
        
        You can get a version of KRN that uses Pth from CVS as explained
in http://krn.sourceforge.net (but don't use it ;-)

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic