From 811b68695dba863198e7e6091f6d2800e74b2817 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 4 Aug 2025 10:53:11 +0200 Subject: [PATCH] glib: add private _g_bit_lock_init() for fast initialization of locked bitlock There will be case where we allocate a new structure that has a bitlock flag. We want to lock on that flag right away (before the pointer is yet shared to other threads). We would usually call g_bit_lock(), but as we are inside glib itself, we *know* that g_bit_lock() will do nothing of relevance except (atomically) setting the lock bit. Note that we don't need the atomic setting, as the pointer is not yet shared. So we can do something cheaper: directly set the bit. Add _g_bit_lock_init() for that. --- glib/gbitlock.c | 29 +++++++++++++++++++++++++++++ glib/gdatasetprivate.h | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/glib/gbitlock.c b/glib/gbitlock.c index 8bcfa7d4e..50f881ca1 100644 --- a/glib/gbitlock.c +++ b/glib/gbitlock.c @@ -244,6 +244,35 @@ g_bit_lock_and_get (gint *address, g_assert (lock_bit < 32u); #endif + /* Beware: _g_bit_lock_init() relies that g_bit_lock() on a newly initialized + * integer (that is not yet shared between threads) is identical to just + * initializing the lock bit without atomic. + * + * In other words: + * + * mystruct = g_new (MyStruct, 1); + * mystruct->flags = 0; + * g_bit_lock (&mystruct->flags, 13); + * return mystruct; // share pointer. + * + * is effectively the same as: + * + * mystruct = g_new (MyStruct, 1); + * mystruct->flags = 0; + * mystruct->flags |= (1 << 13); + * return mystruct; // share pointer. + * + * and the same as: + * + * mystruct = g_new (MyStruct, 1); + * mystruct->flags = 0; + * _g_bit_lock_init (&mystruct->flags, 13); + * return mystruct; // share pointer. + * + * This requires that g_bit_lock() does nothing special in the uncontended + * case (except atomically setting the lock bit). + */ + #ifdef USE_ASM_GOTO if (G_LIKELY (!out_val)) { diff --git a/glib/gdatasetprivate.h b/glib/gdatasetprivate.h index 872a7cd42..2aab01a2f 100644 --- a/glib/gdatasetprivate.h +++ b/glib/gdatasetprivate.h @@ -58,6 +58,46 @@ G_BEGIN_DECLS } \ G_STMT_END +/*< private > + * _g_bit_lock_init: + * @address: (type gpointer): a pointer to an integer + * @lock_bit: a bit value between 0 and 31 + * + * g_bit_lock() in the uncontended case merely atomically sets the bit. Most + * importantly, it does nothing else (of relevance). When we initialize an + * object that is not shared between threads, we don't need this overhead + * and we can set the bit directly. + * + * This is required to have the same effect as g_bit_lock(address, lock_bit), + * as long as we are initializing and the address is not yet accessible to + * other threads. + */ +G_ALWAYS_INLINE static inline void +_g_bit_lock_init (gint *address, gint lock_bit) +{ + *address |= (1 << lock_bit); +} + +/*< private > + * _g_bit_lock_is_locked: + * @address: (type gpointer): a pointer to an integer + * @lock_bit: a bit value between 0 and 31 + * + * Atomically checks whether the lock bit is currently set. + * + * This is only useful in special cases, because usually knowing + * in a multi threaded context whether the lock bit is set is + * irrelevant. Because you cannot know whether the bit gets locked + * right after the check. But there are some uses... + * + * Returns: whether the lock bit is set. + */ +G_ALWAYS_INLINE static inline gboolean +_g_bit_lock_is_locked (gint *address, gint lock_bit) +{ + return !!(g_atomic_int_get (address) & (1 << lock_bit)); +} + /*< private > * GDataListUpdateAtomicFunc: * @data: (inout) (nullable) (not optional): the existing data corresponding to