This site has been retired. For up to date information, see handbook.gnome.org or gitlab.gnome.org.


[Home] [TitleIndex] [WordIndex

1. GDK's lock, its adventures in space! (And also how to use it)

1.1. About

This documentation is not finished. As soon as everything is correct, can an experienced Gtk+ developer please mark it as such?

Gtk+ and Gdk are not thread safe, they are however thread aware (or so they say). To utilize this so-called thread awareness you have two helper functions that will lock and unlock the Gtk+ and Gdk subsystems. You can view it as the BFL (Big Fucking lock) of Gtk+: gdk_threads_enter and gdk_threads_leave.

In general it's advised not to run any code in a thread. Modern applications, however, almost always require background processing and blocking (network) I/O. Users of desktop software will resort to use web applications if they don't care about responsiveness of their applications. A desktop application must be responsive to the user. Usually this, quite simply, implies having to use worker threads.

Regretfully, the development environment or platform for developing such applications with Gtk+ doesn't make this really very simple. This is the reason why we started this wiki page: to explain it in high detail and with no (more) holes in it.

Of course this documentation attempt is not finished. This means that it has holes (it's not complete). I hope other software developers who have experience with Gtk+ and Gdk software development and internals will complete this.

OwenTaylor: there is a false dichotomy in the above: the idea that you either use the GTK+ from multiple threads, or you don't use threads at all. In many cases, the right application architecture is to use non-GUI threads and do all GUI processing from a single thread. When doing that, a useful technique is to use g_idle_add() to mean "run this callback as soon as possible in the main (GUI) thread." As well as keeping things simpler (you don't have to manage the big GDK lock), using only non-GUI threads will also make your code more portable to GTK+ on Windows. Using GTK+ from multiple threads on Windows doesn't work at all, and would require substantial work and complexity to fix.

See also the official documentation of GDK Threads, see: http://developer.gnome.org/doc/API/2.0/gdk/gdk-Threads.html

1.2. When to use it

static gpointer 
my_thread (gpointer user_data) {
   ...
   gdk_threads_enter ();
   gtk_button_do_something_with (button, ...);
   gdk_threads_leave ();
   ...
   g_thread_exit (NULL);
   return NULL;
}

static gboolean 
perform_it (gpointer user_data) {
   gdk_threads_enter ();
   gtk_button_do_something_with (button, ...);
   gdk_threads_leave ();
   return FALSE;
}

static void 
my_function_anywere (void) {
   ...
   g_idle_add_full (G_PRIORITY_DEFAULT, perform_it, 
       NULL, destroy_it)
   ...
}

/* This is a unrecommended style (prefer not to use Gtk+ code in the GDestroyNotify) */
static void
destroy_it (gpointer user_data) {
   gdk_threads_enter ();
   gtk_button_do_something_with (button, ...);
   gdk_threads_leave ();
   return;
}
static void 
my_function_anywere (void) {
   ...
   g_idle_add_full (G_PRIORITY_DEFAULT, perform_it, 
       NULL, destroy_it)
   ...
}

int main (int argv, char **argv)
{
    g_thread_init (NULL);
    gdk_threads_init ();

    gdk_threads_enter ();
    gtk_init (&argc, &argv);
    gtk_main();
    gdk_threads_leave ();

    return 0;
}

1.3. When not to use it

/* This is a DON'T DO THIS sample */
static void
my_func_in_thread (void)
{
   gdk_threads_enter ();
   gtk_button_do_something_with (button, ...);
   gdk_threads_leave ();
   return;
}

static gpointer 
my_thread (gpointer user_data) {
   gdk_threads_enter ();
   my_func_in_thread ();
   gdk_threads_leave ();

   g_thread_exit (NULL);
   return NULL;
}

/* This is a DON'T DO THIS sample */
static void
on_clicked (GtkButton *button, gpointer user_data)
{
   gdk_threads_enter ();
   gtk_button_do_something_with (button, ...);
   gdk_threads_leave ();
   return;
}

static void
make_my_ui (void)
{
   ...
   g_signal_connect (G_OBJECT (button), "clicked",
       G_CALLBACK (on_clicked), NULL);
   ...
   return;
}

/* This is a DON'T DO THIS sample */
static gboolean 
emit_it (gpointer user_data) {
   gdk_threads_enter ();
   g_signal_emit (...)
   return FALSE;
}

static void
destroy_it (gpointer user_data) {
   gdk_threads_leave ();
   return;
}

static gpointer 
my_thread (gpointer user_data) {
   ...
   g_idle_add_full (G_PRIORITY_DEFAULT, emit_it, 
      NULL, destroy_it)
   ...
   g_thread_exit (NULL);
   return NULL;
}

static void 
my_gtk_component_set_something (MyGtkComponent *self, Something something)
{
   MyGtkComponentPriv *priv = MY_GTK_COMPONENT_GET_PRIVATE (self);

   /* Note that this is unrelated but if you do something like this, 
    * you need to unreference in the finalize of MyGtkComponent too */

   if (priv->something)
       g_object_unref (priv->something);
   priv->something = g_object_ref (something);

   g_signal_emit (G_OBJECT (self), 
       MY_GTK_COMPONENT_SOMETHING_GOT_SET_SIGNAL, 0, something);

   return;
}

1.4. Cases that often get forgotten

static gboolean 
emit_it (gpointer user_data) {
   gdk_threads_enter ();
   g_signal_emit (...)
   gdk_threads_leave ();
   return FALSE;
}

static void
destroy_it (gpointer user_data) {
   return;
}

static gpointer 
my_thread (gpointer user_data) {
   ...
   g_idle_add_full (G_PRIORITY_DEFAULT, emit_it, 
      NULL, destroy_it)
   ...
   g_thread_exit (NULL);
   return NULL;
}

/* Although I recommend doing this like the example above in stead,   *
 * by throwing it to the GMainLoop using a g_idle_add_full */
static gpointer 
my_thread (gpointer user_data) {
   ...
   gdk_threads_enter ();
   g_signal_emit (...)
   gdk_threads_leave ();
   ...
   g_thread_exit (NULL);
   return NULL;
}

1.5. Why doing it in the g_idle_add(_full) and g_timeout_add_(full) cases too?

Because if the application developer has a thread that has the lock active, your code that runs in the GMainLoop will run in parallel with this thread. Both threads (the GMainLoop thread and the application developer's thread) can in parallel touch Gtk+ and Gdk subsystems at that time.

Since Gtk+ nor Gdk are thread safe, only thread aware (if you make it aware, and if you do that correctly, using the Gdk Lock) you can't have two things (threads, contexts or interruptable contexts) work on Gtk+'s or Gdk's subsystems in parallel (or semi parallel, in case of interruptable).

1.6. Why doesn't glib solve this for me?

Because glib does not link with Gdk. Therefore it can't launch the gdk_threads_enter and gdk_threads_leave for you. In fact, glib itself is agnostic about the GDK lock.

1.7. Where do g_signal_emit emissions happen? If I do it from a thread then? What?

They happen in the context where you perform g_signal_emit. This means that if you do it from a thread, that the application developer's handlers for it will happen in that thread too. If you want the handlers to happen in the GMainLoop, then you can use g_idle_add(_full) and g_timeout_add(_full). Note that if you want the handlers to behave as signals like the ones emitted by Gtk+ components, that you must wrap them with gdk_threads_enter and gdk_threads_leave. The g_signal_emit wont change anything about the GDK lock's status (it wont explicitly enter, nor leave it).

However, read the next section too

1.8. The GDK functions for threads

This page, however, explains some of the tools that Gdk gives you for playing with threads. The gdk_threads_add_idle(_full) and gdk_threads_add_timeout(_full) will do the wrapping of gdk_threads_enter and gdk_threads_leave, for you. Please consult their documentation, though.

static gboolean
do_perform (gpointer user_data)
{
   gtk_button_do_something_with (button, ...);
   g_signal_emit (...);
   return FALSE;
}

static void
from_anywhere (void)
{
    gdk_threads_add_idle (do_perform, NULL);
}


2024-10-23 10:58