Threading

Summary

  • Do not use threads if at all possible.

  • If threads have to be used, use GTask or GThreadPool and isolate the threaded code as much as possible.

  • Use g_thread_join() to avoid leaking thread resources if using GThread manually.

  • Be careful about the GMainContext which code is executed in if using threads. Executing code in the wrong context can cause race conditions, or block the main loop.

When to use threading

When writing projects using GLib, the default approach should be to never use threads. Instead, make proper use of the GLib main context which, through the use of asynchronous operations, allows most blocking I/O operations to continue in the background while the main context continues to process other events. Analysis, review and debugging of threaded code becomes very hard, very quickly.

Threading should only be necessary when using an external library which has blocking functions which need to be called from GLib code. If the library provides a non-blocking alternative, or one which integrates with a poll() loop, that should be used in preference. If the blocking function really must be used, a thin wrapper should be written for it to convert it to the normal GAsyncResult style of GLib asynchronous function, running the blocking operation in a worker thread.

For example, the following blocking function:

int
some_blocking_function (void *param1,
                        void *param2);

Should be wrapped by this pair of functions:

void
some_blocking_function_async (void                 *param1,
                              void                 *param2,
                              GCancellable         *cancellable,
                              GAsyncReadyCallback   callback,
                              gpointer              user_data);

int
some_blocking_function_finish (GAsyncResult        *result,
                               GError             **error);

With an implementation like:

/* Closure for the call’s parameters. */
typedef struct {
  void *param1;
  void *param2;
} SomeBlockingFunctionData;

static void
some_blocking_function_data_free (SomeBlockingFunctionData *data)
{
  free_param (data->param1);
  free_param (data->param2);

  g_free (data);
}

static void
some_blocking_function_thread_cb (GTask         *task,
                                  gpointer       source_object,
                                  gpointer       task_data,
                                  GCancellable  *cancellable)
{
  SomeBlockingFunctionData *data = task_data;
  int retval;

  /* Handle cancellation. */
  if (g_task_return_error_if_cancelled (task))
    {
      return;
    }

  /* Run the blocking function. */
  retval = some_blocking_function (data->param1, data->param2);
  g_task_return_int (task, retval);
}

void
some_blocking_function_async (void                 *param1,
                              void                 *param2,
                              GCancellable         *cancellable,
                              GAsyncReadyCallback   callback,
                              gpointer              user_data)
{
  GTask *task = NULL;  /* owned */
  SomeBlockingFunctionData *data = NULL;  /* owned */

  g_return_if_fail (validate_param (param1));
  g_return_if_fail (validate_param (param2));
  g_return_if_fail (cancellable == NULL