36.2.2.4 Detecting Single-Threaded Execution

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.

Variable: char __libc_single_threaded

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 __atomic 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)
  ++reference_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 pthread_create or thrd_create function 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 this:

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 __libc_single_threaded 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 such as 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 knowing that these threads are present.