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:
- Prepare: Sources report whether they are already ready and how long the context may sleep.
- Query and poll: The context builds the set of file descriptors and waits until an event or timeout wakes it.
- Check: Sources inspect the poll results and decide whether they are ready.
- 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:
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:
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()andg_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 returnsG_SOURCE_CONTINUEmust yield quickly.g_unix_fd_source_new()watches a Unix file descriptor for aGIOCondition. Child-watch and Unix-signal sources follow the same attach-and-dispatch model.g_io_create_watch()adapts aGIOChannel, 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:
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:
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.
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():
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:
- Store the source and destroy it when the owner is disposed. This bounds the callback’s lifetime to the owner.
- 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:
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.
Comments
Read or comment on GitHub (sign-in required to comment)