|« Main Page|
Table of Contents
To understand threads just think of several processes running at once. Imagine that all these processes have access to the same set of global variables and function calls. Each of these processes would represent a thread of execution and is thus called a thread. The important differentiation is that each thread doesn't have to wait for any other thread to proceed. All the threads can proceed simultaneously. Unlike processes, all threads of one process share the same memory. This is good, as it provides easy communication between the involved threads via this shared memory, and it is bad, because strange things might happen, when the program is not carefully designed.
The main benefit of multi-threading a graphical user interface is increased responsiveness to user requests. One of the more frustrating aspects of both programming and using applications with graphical user interfaces is dealing with operations that take an indeterminate amount of time. Using threads in such an application provides at minimum a more responsive interface and perhaps one that permits more work to occur by allowing the user to queue possible multiple long-running requests.
Thread operations include thread creation, termination, synchronization (joins, blocking), scheduling, data management and process interaction. A thread does not maintain a list of created threads, nor does it know the thread that created it. All threads within a process share the following:
This is a convenience function that initializes the GLib thread system and initializes GDK so that it can be used with multiple threads. There are two parts to the code that this function executes. First, the GLib thread system is initialized:
If g_thread_init() is called twice, the second time it will abort. To make sure this doesn't happen, g::thread::supported() is checked first. It returns false if the GLib thread system has not yet been initialized, and true if it has.
Second, GDK is initialized so that it can be used in multi-threaded applications:
Main::threads::init() should only be called once in a threaded GTK+ program, and must be called before executing any other GTK+ or GDK functions. Most of the time you can just pass null for the 'vtable' argument. You should only call this method with a non-null argument if you really know what you are doing. Do not call threads_init() directly or indirectly as a callback and make sure no mutexes are locked when you make the call. After calling threads_init(), either the thread system is initialized or the program will abort if no thread system is available in GLib (that is, G_THREADS_IMPL_NONE is defined).
GTK+ is "thread aware" but not thread safe, so XFC provides a global lock controlled by Gdk::Mutex::lock() and Gdk::Mutex::unlock() which protects all use of GTK+. That is, only one thread can use GTK+ at any given time. After calling threads_init() you should call Gdk::Mutex::lock() and Gdk::Mutex::unlock() to lock and unlock critical sections of code.
GLib is completely thread safe because it automatically locks all internal data structures as needed. This does not mean that two threads can simultaneously access the same data, but they can access two different instances of the data simultaneously. For performance reasons, individual data structure instances are not automatically locked, so if two different threads need to access the same data, the application is responsible for locking itself.
Idles, timeouts, and input signals are executed outside of the main GTK+ lock. So, if you need to call GTK+ inside of such a callback slot, you must surround the callback with a Gdk::Mutex::lock() and Gdk::Mutex::unlock() pair (all other signals are executed within the main GTK+ lock). In particular, this means, if you are writing widgets that might be used in threaded programs, you must surround timeouts and idle functions in this manner. As always, you must also surround any calls to GTK+ not made within a signal handler with a Gdk::Mutex::lock() and Gdk::Mutex::unlock() pair.
Before calling Gdk::Mutex::unlock() from a thread other than your main thread, you probably want to call Gdk::flush() to send all pending commands to the windowing system. (The reason you don't need to do this from the main thread is that GDK always automatically flushes pending commands when it runs out of incoming events to process and has to sleep while waiting for more events.)
This example doesn't do much but it does show you how to correctly initialize GTK+ in thread-safe mode, and how to lock the main loop (that is, run()).
To create a new thread, call one of the following methods:
Both methods create a thread with the default priority, but the second method lets you specify a stack size. Usually you would use the first create method.
The ThreadSlot argument is a typedef that declares the function signature of the callback (or entry point) to execute in the new thread:
The thread slot can be a member or non-member function and has the form:
The 'joinable' argument sets whether the new thread should be joinable or not. A join is performed when you want to wait for a thread to finish. A thread calling routine may launch multiple threads then wait for them to finish to get the results.To create a new thread and check for an error you could do something like this:
The 'stack_size' and bound arguments are seldom used and best left to those who know what they're doing. The stack_size specifies a stack size for the new thread and bound sets whether the new thread should be bound to a system thread. The G::Error argument is optional and is only set when the create() method returns null.
To join a thread, you call the following method:
The join() method blocks the calling thread until the specified thread terminates. As a recommendation, if a thread requires joining it must be explicitly created as joinable. If you know in advance that a thread will never need to join with another thread, consider creating it in a detached state (joinable = false).
To wait for a thread's completion you would do something like this:
There are two groups of mutexes. The first group includes G::Mutex, G::RecMutex and G::RWLock. These mutexes are used when you want to dynamically create a mutex on the heap or on the stack. G::Mutex is the standard mutex and the one from this group that you will use the most. G::RecMutex is a recursive mutex that can be locked by the same thread multiple times, but before it can be locked by other threads it must be unlocked the same number of times. G::RWLock is a mutex that implements two types of locks, a read-only lock and a write-lock. A read-write lock has a higher overhead than the other mutexes.
The second group of mutexes are analogous to the first but must be created at compiled time (statically), which is sometimes convenient. The names of these mutexes are prefix with static and include G::StaticMutex, G::StaticRecMutex and G::StaticRWLock. These mutexes can be initialized in file scope in an anonymous namespace like this:
The three methods used with mutexes are lock(), trylock() and unlock(). The trylock() and unlock() methods are the same for all mutexes. The lock() method for some mutexes is different because you can optionally specify an argument. For example, the lock() method for G::RecMutex and G::StaticRecMutex looks like this:
The 'depth' argument is for convenience. It lets you specify at lock time the depth, or number of unlocks that must be performed to completely unlock a recursive mutex. You should consult the XFC reference documentation or have a look at the header file <xfc/glib/mutex.hh> for more details.
The following is an example of using G::Condition to block a thread until a condition is satisfied:
The header file for the Thread example is <thread.hh>:
and the source file is <thread.cc>:
If you compiled and installed XFC yourself, you will find the source
code for Thread in the
<examples/howto/thread> source directory along with a Makefile. If
XFC came pre-installed, or you installed it from an RPM package, you
find the source code in the
</usr/share/doc/xfcui-X.X/examples/howto/thread> subdirectory. In
this case you will have to create the Makefile yourself (replace X.X
version number of the libXFCui library you have installed).
|Copyright © 2004-2005 The XFC Development Team||Top