--- category: '' date: 2004/02/03 13:02 description: '' link: '' priority: '' slug: '18' tags: kde, programming, linux title: Cooperative Qt apps using GNU pth type: text updated: 2004/02/03 13:02 url_type: '' --- .. container:: document :name: how-to-make-a-threaded-app-with-qt-kde .. container:: section .. rubric:: Introduction :name: 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. .. container:: section .. rubric:: Why multithreaded apps are good :name: 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: .. code:: C++ 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. .. container:: section .. rubric:: Why multithreaded apps are bad :name: 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 ;-) 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 viceversa. 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. .. container:: section .. rubric:: GNU Pth, and why its good :name: 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? .. code:: C++ 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). .. container:: section .. rubric:: How to make a multithreaded QApplication :name: 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): .. code:: C++ 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 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). .. code:: C++ //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 tricks. 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. .. container:: section .. rubric:: What using Pth won't get you :name: what-using-pth-wont-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): .. code:: C++ class ThreadOwner { public: ThreadOwner() {}; ~ThreadOwner() { QListIterator 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 threads; }; Just make sure you call registerThread when you spawn a thread, releaseThread when the thread exits, and you are probably safe. .. container:: section .. rubric:: Final words: :name: 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/