This tutorial will cover using the Gnome GDK thread library to build a multi-threaded, parallel processing application for cross platform deployment.
GDK threads is a component of the GNOME applications development environment which also includes multi-platform API's for GUIs, graphics, video, window management, XML parsing, internationalization, database access and general application development. The GNOME thread libraries can be used without the GNOME desktop or GUI infrastructure. The API's support the "C" computer language. I usually encapsulate these calls within C++ objects where convenient.
Threads allow one to spawn a new concurrent process flow. It is most effective on multiprocessor systems where the process flow can be scheduled by the operating system to run on another processor thus gaining speed through parallel or distributed processing. The GNOME thread libraries are not a feature rich as POSIX threads but do provide the basic infrastructure.
For more information on programming threads and concepts, see the YoLinux POSIX Threads Tutorial.
For downloading, installation and configuration of the development environment on Ms/Windows with MS/Visual C++ or Cygwin as well as Linux see the YoLinux GTK+ Programming Tutorial
The basic framework of a GDK multi-threaded program requires:
- the include file: gtk/gtk.h
- functions to spawn in a separate thread.
- main program.
- a call to gtk_init (&argc, &argv);
- calls to initialize the threads infrastructure: g_thread_init() and gdk_threads_init().
- spawn a thread: g_thread_create()
- protect data from corruption and contention issues with gdk_threads_mutex
- if not using processing time and you wish to "yield" to other tasks call g_thread_yield()
- if a GTK+ GUI application, protect GTK+ calls using gdk_threads_enter() and gdk_threads_leave()
- Pass conditional information to a thread using g_cond_signal()
- wait to complete thread process: g_thread_join() or terminate (riskier due to race condition potential) using g_thread_exit().
#include <gtk> void *print_message_function( void *ptr ); int main() { GThread *Thread1, *Thread2; char *message1 = "Thread 1"; char *message2 = "Thread 2"; GError *err1 = NULL ; GError *err2 = NULL ; if( !g_thread_supported() ) { g_thread_init(NULL); gdk_threads_init(); // Called to initialize internal mutex "gdk_threads_mutex". printf("g_thread supported\n"); } else { printf("g_thread NOT supported\n"); } if( (Thread1 = g_thread_create((GThreadFunc)print_message_function, (void *)message1, TRUE, &err1)) == NULL) { printf("Thread create failed: %s!!\n", err1->message ); g_error_free ( err1 ) ; } if( (Thread2 = g_thread_create((GThreadFunc)print_message_function, (void *)message2, TRUE, &err2)) == NULL) { printf("Thread create failed: %s!!\n", err2->message ); g_error_free ( err2 ) ; } g_thread_join(Thread1); g_thread_join(Thread2); return 0; } void *print_message_function( void *ptr ) { char *message; g_usleep(1000000); message = (char *) ptr; printf("%s \n", message); g_usleep(1000000); printf("%s \n", message); }
Linux Compile: gcc -o gdk-thread-only gdk-thread-only.c `pkg-config --cflags --libs gtk+-2.0 gthread-2.0`
Results:
[prompt]$ gdk-thread-only g_thread supported Thread 1 Thread 2 Thread 1 Thread 2
A mutex is necessary to protect data from corruption or unexpected behavior.
See the YoLinux POSIX threads tutorial discussion on mutexes and thread synchronization.
Declare mutex outside of thread scope where it is visible to threaded function:
- C++ class member variable initialized in constructor.
- Global variable.
.. ... // Declare outside of thread scope: static GMutex *mutex_to_protext_variable_ABC = NULL; ... .. // Initialize Mutex g_assert (mutex_to_protext_variable_ABC == NULL); mutex_to_protext_variable_ABC = g_mutex_new(); ... .. // Within threaded function, protect variable with a mutex. g_mutex_lock(mutex_to_protext_variable_ABC); ABC = updateFunction(); g_mutex_unlock(mutex_to_protext_variable_ABC); ... ..
Notes:
- All of the g_mutex_* functions are actually macros.
- This is an example of a dynamic mutex created at run time. You may also use a static mutex defined at compile time. I have not had good luck in using static mutexes and find the "dynamic" mutex more usable.
- Considerations when updating GTK+ GUI from a GDK thread
The GDK thread can communicate with the operating system scheduler to yield processing time to other threads. This is preferable to a spinning a loop to delay a thread.
.. ... if ( haveExtraTime ) { if (gtk_events_pending()) { gtk_main_iteration(); // Handle unprocessed GTK events } else { g_thread_yield(); // Yield processing time } } ... ..
[Potential Pitfall]: When using this cross platform API on Microsoft Windows and compiling with Visual Studio VC++ compiler be sure to use the proper compiler flag to use the appropriate libraries:
- Use: /MTd Use debug, multi-threaded, libraries
- DO NOT USE: MLd => Use debug, single-threaded, libraries
- GDK2 Threads Reference Manual
- Glib Ref: Thread Pools - pools of threads to execute work concurrently.
- http://developer.gnome.org
- GTK for Windows: http://gladewin32.sourceforge.net - All in one installer (Ivan Wong)
- YoLinux.com Tutorials: