Document Number: N2227
Submitter: Florian Weimer
Submission Date: 2018-03-26
Subject: Optional thread storage duration for internal function state

Summary

This document proposes to allow implementations to use thread storage duration for the internal state used by the pseudo-random number generator functions and the multibyte/wide conversion functions. It is related to papers N2225, N2226, N2228.

The core observation which motivates this document is that while the standard does not require to detect data races for the affected functions, it does rule out an implementation based on internal state objects with thread storage duration. The reason is that a strictly conforming program can use external synchronization and call these functions from different threads. With internal objects of thread storage duration, the lack of global state sharing would be observable.

  1. Many libraries which are thread-aware (because they use synchronization primitives or even create threads themselves) still call rand, which introduces data races. One way to fix all these libraries is to give the random number generator state optional thread storage duration.

    Another option would be to encourage implementations to avoid data races, either by using locks or atomic memory accesses internally. However, historically, some benchmarks are quite dependent on random number generator performance, so this change might not be desirable to implementors. The implementation in the GNU C library uses a lock.

    POSIX suggests the possiblity of a thread-safe implementation of rand and srand, but does not voice any preference for per-thread state or synchronized access to the global state.

  2. Likewise, the multibyte/wide character and string conversion functions use internal state if the ps argument is a null pointer. For these functions, eliminating the data race would still leave a semantic race condition because the state needs to remain consistent between subsequent calls of these functions.

    The proposal is to give permission to assign thread storage duration to the internal state. The functions have error return values, so the necessary storage could even be allocated dynamically, on demand, to avoid penalizing programs which use an explicit conversion state argument.

    A simpler approach would make it undefined to call the conversion functions from a multi-threaded program (similar to what the signal function does today).

    POSIX suggests that a thread-safe implementation of these functions is possible, but does not go into details. It does not discuss the ability of strictly conforming programs to discover whether the hidden internal state has thread storage duration or not.

Proposed Resolution

  1. In 7.22.2.1 (The rand function), add:
    The rand function is not required to avoid data races with other calls to pseudo-random sequence generation functions. It is implementation-defined whether the internal random number generator state has thread storage duration, and if a newly created thread continues the same sequence of random numbers as the current thread, or if starts again based on the specified seed.
    In 7.22.2.2 (The rand function), change:
    If the pseudo-random number generator state has thread-storage duration, the srand function affects subsequent results of the rand function called by the current thread. Otherwise, the The srand function is not required to avoid data races with other calls to pseudo-random sequence generation functions.
    In J.3.12 (Library functions), add:
    — Whether the random number generator state has thread storage duration, and its initial state in a newly created thread (7.22.2.1, 7.22.2.2).
  2. In 7.28.1 (Restartable multibyte/wide character conversion functions), add:
    If ps is a null pointer, each function uses its own internal mbstate_t object instead, which is initialized at program startup to the initial conversion state; the functions are not required to avoid data races with other calls to the same function in this case. It is implementation-defined whether the internal mbstate_t object has thread-storage definition, and whether a newly created thread has the same state has the current thread at the time of creation, or the initial conversion state. The implementation behaves as if no library function calls these functions with a null pointer for ps.
    In 7.29.6.4 (Restartable multibyte/wide string conversion functions), add:
    If ps is a null pointer, each function uses its own internal mbstate_t object instead, which is initialized at program startup to the initial conversion state; the functions are not required to avoid data races with other calls to the same function in this case. It is implementation-defined whether the internal mbstate_t object has thread-storage definition, and whether a newly created thread has the same state has the current thread at the time of creation, or the initial conversion state. The implementation behaves as if no library function calls these functions with a null pointer for ps.
    In J.3.12 (Library functions), add:
    — Whether the internal mbstate_t object has thread-storage duration, and its initial value in a newly created thread (7.28.1, 7.29.6.4).