Xfce
Foundation Classes |
|||
« Main Page | |||
Multi-Threaded ProgrammingTable of Contents
OverviewMost programmers are used to writing single-threaded programs - that is, programs that only execute one path through their code at a time. Multi-threaded programs may have several threads running through different code paths simultaneously. One of the advantages of multi-threaded programming is that it's considerably cheaper to switch between two threads in a single process than to switch between two processes. Another advantage is that threads can often improve the performance of a program without incurring significant overhead to implement. Be warned though, writing multi-threaded programs requires careful thought. There is the potential to introduce subtle timing faults, or faults caused by the unintentional sharing of variables. Also, debugging a multi-threaded program is much harder than a single-threaded one .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:
Initializing GTK+ in thread-safe modeThe first thing that must be done when writing a multi-threaded program is to initialize GTK+ in thread-safe mode. This is done by calling the 'Main::' namespace function:void threads_init(GThreadFunctions
*vtable = 0); 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_supported()) 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: gdk_threads_init(); 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.) A minimal threaded XFC applicationA minimal main function for a threaded application looks like this:#include <xfc/main.hh> 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()).
Creating a threadCreating a thread in XFC is easy because unlike other C++ thread implementations, your not required to derive a new class or override any virtual functions. Instead G::Thread provides a static function-call that lets you create threads on-the-fly, in any constructor or function body.To create a new thread, call one of the following methods: static Thread* create(const
ThreadSlot& slot, bool joinable, G::Error *error = 0); 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: typedef sigc::slot<void> ThreadSlot; The thread slot can be a member or non-member function and has the form: void function(); 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. #include <iostream> Joining threadsJoining is one way to accomplish synchronization between threads. Two other ways, mutexes and condition variables will be discussed later.To join a thread, you call the following method: void G::Thread::join(); 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: #include <iostream> MutexesMutex is an abbreviation for "mutual exclusion". Mutex variables are one of the primary means of implementing thread synchronization and for protecting shared data when multiple writes can occur. A mutex variable acts like a 'lock' protecting access to a shared data resource. The basic concept of a mutex as used in XFC is that only one thread can lock (or own) a mutex variable at any given time. Thus, even if several threads try to lock a mutex only one thread will be successful. No other thread can own that mutex until the owning thread unlocks that mutex. This ensures that threads take turn in accessing protected data. To prevent data corruption it is important to make sure that every thread that needs to use a mutex does so.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: G::StaticMutex
mutex = XFC_STATIC_MUTEX_INIT; 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: void lock(unsigned int depth = 1); 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.
ConditionsThe condition variable mechanism allows threads to suspend execution and relinquish the processor until some condition is true. A condition variable must always be associated with a mutex to avoid a race condition created by one thread preparing to wait and another thread which may signal the condition before the first thread actually waits on it, resulting in a deadlock. The thread will be perpetually waiting for a signal that is never sent. Any mutex can be used, there is no explicit link between the mutex and the condition variable.G::Condition
*data_cond; // Initialized somewhere else. Thread ExampleThe following thread example is a C++ translation of the GTK+ thread example in the GTK+ FAQ sheet. It's a simple GUI application that displays a window whose only widget is a label.The header file for the Thread example is <thread.hh>: #include <xfc/main.hh> and the source file is <thread.cc>: #include "thread.hh" Compiling Thread
If you compiled and installed XFC yourself, you will find the source
code for Thread in the
<examples/thread> source directory along with a Makefile. If
XFC came pre-installed, or you installed it from an RPM package, you
will
find the source code in the
</usr/share/doc/xfcui-X.X/examples/thread> subdirectory. In
this case you will have to create the Makefile yourself (replace X.X
with the
version number of the libXFCui library you have installed). |
Copyright © 2004-2005 The XFC Development Team | Top |
XFC
4.4 |