August 1, 2018·4 min read·engineeringcglibcthreadsconcurrency

glibc 2.28 implements C threads through its existing runtime

glibc 2.28 supplies the ISO C thread interfaces in threads.h. Its implementation adapts entry-point results and error codes while sharing the underlying pthread machinery.

An implementation of the C thread library needs more than aliases for pthread function names. The thread entry point returns a different kind of result, and failures use the C thread library's status codes. glibc 2.28, released on August 1, adds all the standard functions declared by threads.h while adapting those differences to its existing thread runtime.

The release announcement explicitly requires applications to link against libpthread. Selecting a C language mode is therefore insufficient to use the new interfaces. The source header and library implementation must be present, and the final link must include the thread library. This is a library-availability change with a concrete build requirement.

C's importance to me includes the interfaces through which other code reaches operating-system facilities. A standard threading vocabulary is useful at that boundary, but the implementation details still matter. Sharing an existing runtime is sensible only if the adapter preserves the semantics of the interface it exposes.

The entry function's integer result needs an adapter

The 2.28 implementation of thrd_create calls the internal pthread creation function with a special ATTR_C11_THREAD attribute value. It passes the C thread entry point through the internal function-pointer representation and maps the creation result to a C thread status.

Reading that file alone could give a misleading impression. Its function-pointer cast does not mean that the runtime simply invokes a C entry point through an incompatible signature. The shared thread-start implementation recognizes the C11-thread marker and casts the entry point back before calling it.

That branch calls a function returning int. It then converts the integer result through uintptr_t into the runtime's stored pointer representation. Ordinary pthread entry points take the other branch. The same thread descriptor can therefore support both public interfaces while preserving the correct call signature at the moment the application function executes.

The marker also controls attribute handling during creation. The internal function recognizes the special value and selects the default thread attributes. Later it records the C11 flag in the new thread's descriptor. The flag connects the public creation operation to the correct startup branch, rather than leaving the return-type distinction to an accidental calling-convention match.

This is a useful implementation pattern for a language-level wrapper. The common runtime handles thread creation and execution, while the descriptor records the semantic difference needed when invoking the application. The wrapper does not need a second independent thread subsystem to expose a different standard API.

Joining recovers the result in the C interface's form

The thrd_join implementation uses the internal join machinery to obtain the stored result. When the caller supplies a result pointer, it converts the returned representation through uintptr_t back to int. It then maps the join operation's status with the same helper used by creation. A small program shows the two distinct results involved:

#include <threads.h>

static int worker(void *argument)
{
    (void)argument;
    return 7;
}

int main(void)
{
    thrd_t thread;
    int result;

    if (thrd_create(&thread, worker, 0) != thrd_success)
        return 1;
    if (thrd_join(thread, &result) != thrd_success)
        return 2;
    return result == 7 ? 0 : 3;
}

The return from thrd_join reports whether joining succeeded. The value stored in result is the application function's return value. They are independent pieces of information. Testing only the stored integer would not establish that the join operation itself completed successfully.

For this release, a corresponding build includes the thread option. This satisfies the announcement's requirement to link the thread library:

cc -std=c11 -pthread example.c -o example

The command assumes glibc 2.28's headers and libraries are the ones used by the toolchain. It does not make those interfaces available on an older library installation. The announcement's linking requirement is particularly important for a program whose source compiles successfully before the missing library dependency becomes visible at link time.

Error translation reduces the platform-specific vocabulary

The internal status mapping helper shows the adapter's other responsibility. It maps zero to thrd_success, ENOMEM to thrd_nomem, and ETIMEDOUT to thrd_timedout. EBUSY becomes thrd_busy, while other errors become thrd_error.

That mapping has a deliberate information boundary. A caller using the C interface receives its standardized status categories. It does not receive every possible internal error code through the return value. Code that needs a more detailed platform-specific error vocabulary has a different requirement from code satisfied by these categories.

I find that distinction useful when choosing an interface. A smaller status vocabulary can simplify portable application logic, but it also limits the conclusions a caller can draw from a generic error. The wrapper should be evaluated by the contract it offers, without assuming that sharing pthread internals exposes the complete pthread interface.

The release includes the surrounding synchronization facilities

The announcement lists mutex and condition-variable operations alongside thread management. It also includes call_once and the thread-specific-storage functions. The addition therefore covers the standard synchronization and per-thread storage vocabulary, not only launching and joining a worker.

That breadth matters when considering whether an application can use the C interface consistently. Thread creation alone would leave ordinary coordination dependent on another API. Supplying the standard mutex, condition-variable and storage operations lets code express those parts of its design through the same library contract.

Other changes in the same announcement demonstrate that library compatibility extends beyond symbol availability. The release makes end-of-file a sticky stdio condition. If another process enlarges a file after the reader has reached EOF, the reader must clear that condition or use an operation with the same effect before reading the additional data. The announcement identifies this as a longstanding conformance correction. A library upgrade can therefore both supply a new thread interface and change an existing program's observable input behavior. The relevant contract depends on which library operation the application uses, even when its source-language mode stays unchanged.

The implementation still relies on the existing runtime beneath that contract. Its special creation marker preserves the entry function's integer return, the join adapter recovers it, and the status helper translates internal failures. These concrete adaptations explain what glibc 2.28 adds beyond another set of exported names.