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

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

When to use it

  • When you are going to launch code that touches Gtk+ or Gdk subsystems from a thread that does not yet hold the GDK lock

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;
}
  • When you are going to launch code that touches Gtk+ or Gdk subsystems from a g_idle_add(_full) and g_timeout_add(_full) callback (Code run immediately from the mainloop, such as timeouts and idles)

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)
   ...
}
  • When you are going to launch code that touches Gtk+ or Gdk subsystems from a g_idle_add_full and g_timeout_add_full 's GDestroyNotify. According to OwenTaylor, for style reasons, if nothing else, you want to avoid doing GTK+/GDK work from GDestroyNotify.

/* 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)
   ...
}
  • In case you are going to use threads that will touch subsystems of Gtk+ or Gdk: around the gtk_main() invocation

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;
}

When not to use it

  • Recursively (when you are already holding the lock). Regretfully isn't the BFL a recursive one (yet, I don't know why it's not. And Indeed, I think it should). (OwenTaylor: the reasons that the GTK+ lock is not recursive are mostly historical... at that point in time we didn't have an easy way to do recursive locks cross-platform. But recursive locks are not the panacea that you might think; if you have code that is called both inside and outside the GTK+ lock it is likely vulnerable to other deadlocks. See: http://article.gmane.org/gmane.comp.gnome.bindings.java.devel/798 for some discussion of this.)

/* 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;
}
  • From signal handlers that got emitted by Gtk+ components (like the clicked event of a GtkButton). It appears that Gtk+ emits its signals while it has the lock active. That's because all of Gtk+'s code should/must run in the GDK lock already (and an emission of a signal doesn't change that).

/* 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;
}
  • It's incorrect to use the Gdk Lock like the sample below. GLib could legitimately call arbitrary amounts of other callbacks before it destroys the source by calling your GDestroyNotify. If they tried to get the Gdk lock, it would deadlock. You'd also block all other threads.

/* 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;
}
  • In normal code that is normally happening (for example in the GMainLoop).

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;
}

Cases that often get forgotten

  • If you throw the emission of a signal (g_signal_emit) to the GMainLoop using g_idle_add(_full) or g_timeout_add(_full), the emission does not have the GDK lock. You can for example wrap the g_signal_emit with a gdk_threads_enter and gdk_threads_leave to make the emission act like signal emissions that come from Gtk+ components. Side note by OwenTaylor: If you are counting on the GDK lock to protect you when removing a timeout/idle in an object's destructor, then you need to use gdk_threads_add_idle() gdk_threads_add_timeout(), since there are otherwise race conditions between the source being destroyed and entering the Gdk lock. These functions can also just be used for a bit of convenience. However, if you have other locks involved, make sure that using these functions don't cause problems with lock ordering.

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;
}
  • It's a misconception that g_signal_emit escapes the context and makes the handlers of the signal happen in the GMainLoop. It's also a misconception that if you launch a g_signal_emit from a thread, that you don't need to wrap it with the Gdk lock. This depends on what the handlers will do. If you want the handlers to touch Gtk+ or Gdk subsystems, then you most certainly must do this.

/* 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;
}

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).

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.

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

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);
}

Attic/GdkLock (last edited 2013-11-26 21:18:10 by WilliamJonMcCann)