Multi-threaded programs require synchronization among threads. This synchronization can be costly even if there is just a single thread and no data is shared between multiple processors. The GNU C Library offers an interface to detect whether the process is in single-threaded mode. Applications can use this information to avoid synchronization, for example by using regular instructions to load and store memory instead of atomic instructions, or using relaxed memory ordering instead of stronger memory ordering.
This variable is non-zero if the current process is definitely single-threaded. If it is zero, the process may be multi-threaded, or the GNU C Library cannot determine at this point of the program execution whether the process is single-threaded or not.
Applications must never write to this variable.
Most applications should perform the same actions whether or not
__libc_single_threaded is true, except with less
synchronization. If this rule is followed, a process that
subsequently becomes multi-threaded is already in a consistent state.
For example, in order to increment a reference count, the following
code can be used:
if (__libc_single_threaded) atomic_fetch_add (&reference_count, 1, memory_order_relaxed); else atomic_fetch_add (&reference_count, 1, memory_order_acq_rel);
This still requires some form of synchronization on the
single-threaded branch, so it can be beneficial not to declare the
reference count as
_Atomic, and use the GCC
built-ins. See Built-in Functions for Memory
Model Aware Atomic Operations in Using the GNU Compiler Collection
(GCC). Then the code to increment a reference count looks like this:
if (__libc_single_threaded) ++refeference_count; else __atomic_fetch_add (&reference_count, 1, __ATOMIC_ACQ_REL);
(Depending on the data associated with the reference count, it may be
possible to use the weaker
__ATOMIC_RELAXED memory ordering on
the multi-threaded branch.)
Several functions in the GNU C Library can change the value of the
__libc_single_threaded variable. For example, creating new
threads using the
sets the variable to false. This can also happen indirectly, say via
a call to
dlopen. Therefore, applications need to make a copy
of the value of
__libc_single_threaded if after such a function
call, behavior must match the value as it was before the call, like
bool single_threaded = __libc_single_threaded; if (single_threaded) prepare_single_threaded (); else prepare_multi_thread (); void *handle = dlopen (shared_library_name, RTLD_NOW); lookup_symbols (handle); if (single_threaded) cleanup_single_threaded (); else cleanup_multi_thread ();
Since the value of
__libc_single_threaded can change from true
to false during the execution of the program, it is not useful for
selecting optimized function implementations in IFUNC resolvers.
Atomic operations can also be used on mappings shared among
single-threaded processes. This means that a compiler must not use
__libc_single_threaded to optimize atomic operations, unless it
is able to prove that the memory is not shared.
Implementation Note: The
variable is not declared as
volatile because it is expected
that compilers optimize a sequence of single-threaded checks into one
check, for example if several reference counts are updated. The
current implementation in the GNU C Library does not set the
__libc_single_threaded variable to a true value if a process
turns single-threaded again. Future versions of the GNU C Library may do
this, but only as the result of function calls which imply an acquire
(compiler) barrier. (Some compilers assume that well-known functions
malloc do not write to global variables, and setting
__libc_single_threaded would introduce a data race and
undefined behavior.) In any case, an application must not write to
__libc_single_threaded even if it has joined the last
application-created thread because future versions of the GNU C Library may
create background threads after the first thread has been created, and
the application has no way of knowning that these threads are present.