Cooperative Qt apps using GNU pth

How to make a threaded app with Qt/KDE

Introduction

The pur­po­se of this do­cu­ment is to ex­plain the pros and cons of im­ple­men­ting a mul­ti­th­rea­ded KDE (or Qt) appli­ca­tio­n, as we­ll as ex­plai­ning brie­fly the way in whi­ch that can be do­ne.

Why multithreaded apps are good

Mul­ti­th­rea­ded apps are good in that they allow you to ha­ve a ­no­n-­blo­cking GUI wi­thout do­ing mu­ch tri­cke­r­y. For ins­tan­ce, when you want to per­form a len­gthy cal­cu­la­tion in a sin­gle­th­rea­ded Qt a­ppli­ca­tio­n, you need to put so­me pro­ce­ssE­ven­ts() ca­lls in be­tween ­len­gthy ope­ra­tions like this:

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

This has a se­rious do­wn­si­de: whi­le you wi­ll ha­ve a UI that re­draws itsel­f ­co­rrec­tly (as long as do­So­me­thin­g() does­n’t take lon­g), you STI­LL need to­ ­blo­ck (or ra­the­r, di­sa­ble) most of your UI.

If you do­n’t di­sa­ble part of the UI, qA­pp->­pro­ce­ssE­ven­ts() wi­ll get a ­bu­tton press and send your app to do so­me­thing el­se en­ti­re­ly making you­r ­li­fe di­ffi­cul­t, or you ha­ve to wri­te your app to be able to ju­ggle se­pa­ra­te ca­lls to do­Lon­gThin­g() at the sa­me ti­me, for exam­ple.

The­re is an al­ter­na­ti­ve, whi­ch is mo­ving len­gthy ope­ra­tions in­to se­pa­ra­te ­pro­ce­sses, and just ha­ve your app wait for the se­pa­ra­te pro­ce­s­ses to en­d. ­This is the appro­ach us­ed in KDE for KIO. This is good! This is hard to do­ we­ll, just ask the gu­ys who de­sig­ned KIO ;-)

On a mul­ti­th­rea­ded app, you would not do a qA­pp->­pro­ce­ssE­ven­ts() an­d ­sim­ply lo­op at wi­ll. You wo­n’t di­sa­ble the UI be­cau­se the­re is no a prio­ri ­rea­son why you can’t do se­ve­ral do­Lon­gThin­g() at the sa­me ti­me, just make ­su­re ea­ch one runs in its own th­rea­d.

Why multithreaded apps are bad

Mul­ti­th­rea­ded apps are bad in se­ve­ral wa­ys. For one thin­g, the­re is no­ way to im­ple­ment a rea­lly mul­ti­th­rea­ded app that uses Qt from se­ve­ral th­rea­d­s at on­ce, be­cau­se Qt is not reen­tran­t.

Wha­t’s being reen­tran­t? Let me quo­te Ralf S. En­gels­cha­ll that kno­ws ­mu­ch mo­re than I do ;-)


reen­trant th­rea­d-s­afe and as­yn­ch­ro­nous-s­afe func­tion­s:

A reen­trant func­tion is one that be­ha­ves co­rrec­tly if it is ca­lled si­mul­ta­neous­ly by se­ve­ral th­rea­ds and then al­so­ exe­cu­tes si­mul­ta­neous­l­y. Func­tions that ac­ce­ss glo­ba­l s­ta­te, su­ch as me­mo­ry or fi­le­s, of cour­se, need to be­ ­ca­re­fu­lly de­sig­ned in or­der to be reen­tran­t. Two­ ­tra­di­tio­nal appro­aches to sol­ve the­se pro­ble­ms are ­ca­lle­r-­su­pplied sta­tes and th­rea­d-s­pe­ci­fic da­ta.

Th­rea­d-s­afe­ty is the avoi­dan­ce of da­ta ra­ce­s, i.e., ­si­tua­tions in whi­ch da­ta is set to ei­ther co­rrect or in­co­rrect va­lue de­pen­ding upon the (un­pre­dic­ta­ble) or­de­r in whi­ch mul­ti­ple th­rea­ds ac­ce­ss and mo­di­fy the da­ta. So a ­func­tion is th­rea­d-s­afe when it sti­ll be­ha­ves se­man­ti­ca­ll­y ­co­rrect when ca­lled si­mul­ta­neous­ly by se­ve­ral th­rea­ds (i­t is not re­qui­red that the func­tions al­so exe­cu­te ­si­mul­ta­neous­l­y). The tra­di­tio­nal appro­ach to achie­ve ­th­rea­d-s­afe­ty is to wrap a func­tion body wi­th an in­ter­na­l ­mu­tual ex­clu­sion lo­ck (aka `mu­tex’). As you shoul­d ­re­cog­ni­ze, reen­trant is a stron­ger attri­bu­te than th­rea­d- s­afe, be­cau­se it is har­der to achie­ve and re­sul­ts es­pe­cia­lly in no run-­ti­me con­ten­tion be­tween th­rea­d­s. So­, a reen­trant func­tion is alwa­ys th­rea­d-s­afe, but not vi­ce­ver­sa.

Sys­tem Me­ss­age: WAR­NIN­G/2 (x, li­ne 74); ba­ck­li­nk

In­li­ne in­ter­pre­ted text or ph­ra­se re­fe­ren­ce star­t-s­tring wi­thout en­d-s­trin­g.

So, you CAN im­ple­ment a mul­ti­th­rea­ded Qt appli­ca­tio­n, as long as you keep all your Qt (and KDE) re­lated func­tion ca­lls in a sin­gle th­rea­d, and mo­ve to other th­rea­ds things like nu­me­ri­cal cal­cu­la­tion­s.

Sad­l­y, this is a real pain in a KDE appli­ca­tio­n, be­cau­se we do LO­TS of things using Qt and KDE libs. For ins­tan­ce, I use kfi­leio (part of ke­di­t, I be­lie­ve) to load text fi­les in­to me­mo­r­y, be­cau­se it sho­ws ni­ce war­ning bo­xes when so­me­thing goes wron­g. I can’t do that from a se­pa­ra­te ­th­rea­d. Tha­t’s not good.

The­re is al­so a now mi­nor pro­ble­m: real th­rea­ds (P­th­rea­d­s) are not all that por­ta­ble. Ho­we­ver the­se da­ys most unix-­like sys­te­ms su­pport them in their la­test ver­sion­s, so it’s not as bad as it us­ed to be.

GNU Pth, and why its good

Sin­ce real mul­ti­th­rea­ded apps are not all that po­s­si­ble, and good no­n-­mul­ti­th­rea­ded apps are not too ea­sy to wri­te in so­me si­tua­tion­s, I looked aroun­d, and found GNU Pth, the GNU Por­ta­ble th­rea­ds li­bra­r­y.

GNU Pth does coope­ra­ti­ve userspa­ce mul­ti­th­rea­din­g. What does that mean?

Userspa­ce means that it does­n’t re­qui­re ker­nel su­ppor­t.

That helps wi­th the por­ta­bi­li­ty pro­ble­m. GNU Pth wo­rks on sys­te­ms like li­b­c5 li­nux whe­re pth­rea­ds do­n’t exist (or su­ck).

Coope­ra­ti­ve mul­ti­th­rea­ding means that th­rea­ds are not sus­pen­ded to allow for the exe­cu­tion of ano­ther th­read un­til they yield con­tro­l, or sus­pend im­pli­ci­tly by ca­lling so­me func­tions tha­t, we­ll, sus­pend them whi­le wai­tin­g ­for so­me event (like I/O­).

To tho­se he­re who are now thi­nking of Win­do­ws 3.1 and Ma­cin­tos­h’s coope­ra­ti­ve mul­ti­ta­skin­g, do­n’t be too afrai­d, you on­ly need to coope­ra­te wi­th your­sel­f, so it’s not that cra­ppy :-)

So, how would our co­de look using GNU Pth?

void doLongThing()
{
    bool a;
    for (;a;)
    {
        a=doSomething();
        pth_yield(NULL);
    }
}

Not all that di­ffe­rent :-)

Ho­we­ve­r, the­re is a big di­ffe­ren­ce: run­ning se­ve­ral do­Lon­gThin­g()s at the sa­me ti­me is ra­ther na­tu­ra­l, as long as you make su­re ea­ch runs in a se­pa­ra­te th­rea­d, and you do­n’t rea­lly need to di­sa­ble the UI!

Al­so, sin­ce using coope­ra­ti­ve MT means that your co­de is not preemp­te­d, you CAN ca­ll Qt and KDE­Libs from the se­con­da­ry th­rea­d­s, wi­th so­me ve­ry mi­nor ca­vea­ts (whi­ch we wi­ll see).

How to make a multithreaded QApplication

He­re is the sim­ple cla­ss I use to make a mul­ti­th­rea­ded QA­pp (mo­di­fy in the ob­vious way to make it a KA­pp):

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;)
        {
                //Process any events
                processEvents();
                //wait 1/1000 of a second, so we don't hog
                //the CPU and let other threads execute.
                pth_nap(pth_time(0,1000));
        }
}

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

What does it do? It sim­ply ini­tia­li­zes and clo­ses the Pth li­bra­ry in the ri­ght mo­men­ts, and yiel­ds pro­ce­s­sing to the other th­rea­ds 1000 ti­mes per se­cond (mo­re or le­ss).

Even if exe­c() see­ms to be a CPU ho­g, it is no­t. CPU usage is not mea­su­ra­ble on a id­le appli­ca­tio­n.

No­w, whi­le this is te­ch­ni­ca­lly a mul­ti­th­rea­ded appli­ca­tio­n, it’s a one-­th­read MT app, whi­ch is not too in­te­res­tin­g.

What you need to do to take ad­van­ta­ge of Pth, is make your appli­ca­tion spawn new th­rea­ds whe­ne­ver you start do­ing so­me­thing that takes lon­g.

So, su­ppo­se a bu­tton has a slot con­nec­ted to its cli­cke­d() sig­na­l, and that slot is ca­lled slo­tDo­Lon­gThin­g().

He­re is how that slot wi­ll look (but it can be do­ne pre­ttie­r, I be­t).

//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, whe­ne­ver slo­tDo­Lon­gThing is ca­lle­d, it wi­ll spawn a new th­read to do­So­me­thin­g() a long whi­le, and your UI wi­ll not blo­ck (be­cau­se you ca­ll pth_­yiel­d(), or at leas­t, it wi­ll blo­ck as li­ttle as it would if you ca­lled pro­ce­ssE­ven­ts(), and you do­n’t need to di­sa­ble the UI be­cau­se mul­ti­ple ­do­Lon­gThings can be do­ne wi­thout any fur­ther pro­gra­m­ming tri­cks.

As a ru­le, I ne­ver ca­ll pro­ce­ssE­ven­ts() from any of the spaw­ned th­rea­ds be­cau­se it does­n’t make sen­se: you get in the sa­me me­ss as if you we­re not using Pth! I just pth_­yiel­d(­NU­LL), and even­tua­lly it wi­ll get to­ a pro­ce­ssE­ven­ts() in the main th­rea­d.

What using Pth won’t get you

You wo­n’t take ad­van­ta­ge of mul­ti­ple CPUs. You need real Pth­rea­ds for tha­t.

You do­n’t get no­n-­blo­cking I/O­… un­le­ss you use pth_­read ins­tead of rea­d(), pth_w­ri­te() ins­tead of wri­te() and so on. You may or may not be able to take ad­van­ta­ge of tha­t, de­pen­ding on your appli­ca­tio­n.

You do­n’t get PER­FEC­TLY no­n-­blo­cking UI. You get a mos­tly no­n-­blo­cking one. If you do­n’t ca­ll pth_­yiel­d(), you WI­LL blo­ck. In most ca­ses, you get a GOOD ENOU­GH no­n­blo­cking UI, thou­gh.

You do­n’t get rid of da­ta ra­ce­s. You get rid of MOST da­ta ra­ce­s, but you can get so­me. Con­si­der what ha­ppens in the exam­ple if so­meo­ne de­le­tes the bu­tton :-) You can fix most of the­se things by making ob­jec­ts abort th­rea­d­s ­that hand­le re­fe­ren­ces to the­m. he­re’s what I use (wi­th a bit of mul­ti­ple inhe­ri­tan­ce):

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 su­re you ca­ll re­gis­ter­Th­read when you spawn a th­rea­d, re­lea­se­Th­read when the th­read exi­ts, and you are pro­ba­bly safe.

Final words:

Whi­le this is by no means a uni­ver­sal so­lu­tio­n, I ha­ve found it use­fu­l.

I am su­re the­re are lo­ts of im­pro­ve­men­ts be­tter pro­gra­m­mers than I can do, so do­n’t be shy to ex­plain them to me :-)

You can get GNU Pth from http://www.g­nu.or­g/­so­ftwa­re/p­th/

Comentarios

Comments powered by Disqus
Contents © 2000-2013 Roberto Alsina
Share