GLib Main Loop

Last update: 2026-05-24 01:10:27 +0800 +0800
tags: Glib | Gtk
Available translations: 日本語 / 中文(简体) / 中文(繁體)

GLib’s event loop separates event descriptions from event dispatch. A GSource describes when work is ready, a GMainContext owns and schedules a set of sources, and a GMainLoop repeatedly iterates one context. Keeping those roles separate makes callback placement, cancellation, and lifetime rules explicit.

Sources, Contexts, and Loops #

One iteration of a main context has four conceptual phases:

  1. Prepare: Sources report whether they are already ready and how long the context may sleep.
  2. Query and poll: The context builds the set of file descriptors and waits until an event or timeout wakes it.
  3. Check: Sources inspect the poll results and decide whether they are ready.
  4. Dispatch: Ready sources invoke their callbacks.

g_main_context_iteration() performs one iteration. The lower-level g_main_context_prepare(), g_main_context_query(), g_main_context_check(), and g_main_context_dispatch() calls exist for integration with another event loop, but an application must preserve their ordering and context-acquisition rules.

GMainLoop adds a running flag around that mechanism:

c
GMainContext *context = g_main_context_new();
GMainLoop *loop = g_main_loop_new(context, FALSE);

g_main_loop_run(loop);
/* A callback eventually calls g_main_loop_quit(loop). */

g_main_loop_unref(loop);
g_main_context_unref(context);

g_main_loop_quit() stops the loop after the current dispatch returns. It does not destroy the context or its sources. Multiple loops may iterate the same context, including nested loops, so nested execution permits re-entrancy into every source attached to that context. GTK applications should prefer asynchronous APIs over nested loops because synchronous-looking code can otherwise dispatch unrelated UI callbacks.

Readiness, Priority, and Ownership #

Every source has an integer priority. Smaller numbers mean higher priority: G_PRIORITY_HIGH runs before G_PRIORITY_DEFAULT, while G_PRIORITY_DEFAULT_IDLE is lower. When sources at different priorities are ready, GLib dispatches the higher-priority work first. A continuously ready high-priority source can therefore starve lower-priority sources. Code must not depend on an undocumented ordering among sources at the same priority.

Attaching a source associates it with at most one context:

c
GSource *source = g_timeout_source_new(250);

g_source_set_priority(source, G_PRIORITY_DEFAULT);
g_source_set_callback(source, refresh_cache, cache, NULL);
guint source_id = g_source_attach(source, context);
g_source_unref(source); /* The context now keeps the source alive. */

The callback returns G_SOURCE_CONTINUE to remain scheduled or G_SOURCE_REMOVE to destroy the source after dispatch. g_source_destroy() is the direct cancellation operation and may be called from another thread. Source IDs are context-specific and may eventually be reused; retaining a GSource * is less ambiguous when code owns a non-default context.

Attaching transfers no application-data ownership by itself. Pass a GDestroyNotify to g_source_set_callback() or a *_add_full() helper when callback data needs cleanup. The notify function runs when the callback is replaced or the source is finalized, and it must not assume a particular thread.

Built-In and Custom Sources #

GLib provides source constructors for common readiness conditions:

  • g_timeout_source_new() and g_timeout_source_new_seconds() become ready after an interval. Repeating timeouts schedule the next interval from GLib’s monotonic-time calculation; they are not exact periodic real-time timers.
  • g_idle_source_new() is ready when no higher-priority work is ready. An idle callback that returns G_SOURCE_CONTINUE must yield quickly.
  • g_unix_fd_source_new() watches a Unix file descriptor for a GIOCondition. Child-watch and Unix-signal sources follow the same attach-and-dispatch model.
  • g_io_create_watch() adapts a GIOChannel, although new Unix code can often watch a nonblocking file descriptor directly.

The convenience functions g_timeout_add(), g_idle_add(), and g_child_watch_add() attach to the global-default context, not the current thread-default context. Context-aware library code should construct the source and attach it explicitly:

c
GSource *source = g_idle_source_new();

g_source_set_callback(source, publish_result, result, result_free);
g_source_attach(source, target_context);
g_source_unref(source);

A custom source embeds GSource as its first field and supplies GSourceFuncs:

c
typedef struct {
  GSource source;
  gboolean pending;
} WorkSource;

static gboolean
work_prepare(GSource *source, gint *timeout_ms)
{
  WorkSource *work = (WorkSource *) source;
  *timeout_ms = -1;
  return work->pending;
}

static gboolean
work_check(GSource *source)
{
  return ((WorkSource *) source)->pending;
}

static gboolean
work_dispatch(GSource *source, GSourceFunc callback, gpointer data)
{
  WorkSource *work = (WorkSource *) source;
  work->pending = FALSE;
  return callback(data);
}

static GSourceFuncs work_source_funcs = {
  work_prepare, work_check, work_dispatch, NULL
};

Create an instance with g_source_new(&work_source_funcs, sizeof(WorkSource)). A source that waits on kernel events should register poll descriptors rather than repeatedly report itself ready; otherwise it turns the loop into a busy wait. If another thread changes custom-source state, protect that state and call g_main_context_wakeup() so a blocked poll observes the change.

Thread-Default Contexts #

The global-default context returned by g_main_context_default() is distinct from the per-thread stack of thread-default contexts. GIO asynchronous operations commonly capture the calling thread’s thread-default context and deliver completion there.

c
g_main_context_push_thread_default(worker_context);
start_async_operation();
g_main_context_pop_thread_default(worker_context);

Every push must have a matching pop in the same thread. If the stack is empty, g_main_context_get_thread_default() returns NULL, while g_main_context_ref_thread_default() returns a referenced thread-default context or the global default. Libraries should document the context in which each callback and signal is delivered instead of silently assuming the global default.

A context can be acquired by only one thread at a time, although ownership may move between iterations. To send work to a context normally run by another thread, use g_main_context_invoke_full():

c
g_main_context_invoke_full(ui_context,
                           G_PRIORITY_DEFAULT,
                           apply_worker_result,
                           result,
                           result_free);

The function executes while ui_context is owned. Invocation may happen directly when the caller already owns the context. It may also run directly when the context is the caller’s thread-default context and the caller can acquire it; thus it is not an unconditional asynchronous queue operation. A design that requires thread affinity should keep the target context running and owned by its designated thread, then assert the expected context with g_main_context_is_owner(). Use GTask when the requirement is background execution rather than a particular worker thread.

Callback Lifetimes #

An attached source can outlive the object represented by its callback data. GLib documents two primary ownership strategies:

  1. Store the source and destroy it when the owner is disposed. This bounds the callback’s lifetime to the owner.
  2. Give the source a strong reference and release it with GDestroyNotify. This prevents use-after-free, but a source that never runs or is never destroyed can keep the owner alive indefinitely.

A weak reference is useful when the operation may harmlessly disappear with its target:

c
typedef struct {
  GWeakRef target;
} PendingUpdate;

static gboolean
apply_update(gpointer user_data)
{
  PendingUpdate *pending = user_data;
  g_autoptr(GObject) target = g_weak_ref_get(&pending->target);

  if (target == NULL)
    return G_SOURCE_REMOVE;

  /* Update target while the temporary strong reference is held. */
  return G_SOURCE_REMOVE;
}

static void
pending_update_free(gpointer data)
{
  PendingUpdate *pending = data;
  g_weak_ref_clear(&pending->target);
  g_free(pending);
}

static void
schedule_update(GObject *target, GMainContext *target_context)
{
  PendingUpdate *pending = g_new0(PendingUpdate, 1);
  GSource *source = g_idle_source_new();

  g_weak_ref_init(&pending->target, target);
  g_source_set_callback(source, apply_update, pending, pending_update_free);
  g_source_attach(source, target_context);
  g_source_unref(source);
}

GWeakRef is thread-safe: g_weak_ref_get() atomically obtains a strong reference or returns NULL. Raw weak pointers installed with g_object_add_weak_pointer() do not provide that cross-thread guarantee. Weak references solve object validity, not synchronization; mutable state still needs confinement to one context or explicit locking.

Upstream References #

Comments

Read or comment on GitHub (sign-in required to comment)