Project: Programming Language C++
Reply to: Beman Dawes <bdawes at acm.org>
Peter Dimov <pdimov at pdimov.com>
Herb Sutter <hsutter at microsoft.com>
Hans Boehm <Hans.Boehm at hp.com>
Lawrence Crowl <crowl at google.com>
Paul E. McKenney <paulmck at linux.vnet.ibm.com>
Jeffrey Yasskin <jyasskin at google.com>
Unless otherwise specified, standard library classes may safely be instantiated from multiple threads and standard library functions are reentrant, but non-const use of objects of standard library types is not safe if shared between threads. Use of non-constness to determine thread-safety requirements ensures consistent thread-safety specifications without having to add additional wording to each and every standard library type.
With the introduction of multi-threading into the C++ standard, the contract between standard library users and implementers needs to explicitly state the conditions under which standard library components are or are not thread-safe.
The objective is to offer users of the standard library as much thread-safety as is possible without impacting performance or creating an illusion of thread-safety where none exists.
The basic thread-safety guarantee would be that standard library functions are required to be reentrant, and non-mutating uses of objects of standard library types are required to not introduce data races. This has little or no impact on performance. It does actually deliver the promised safety. Thus this basic thread-safety guarantee is required of implementations.
The strong thread-safety guarantee would be that mutating uses of objects of standard library types are required to not introduce data races. This would have a severe negative impact on performance. Furthermore, real safety often requires locking across several member function calls, so providing per function-call locking would create a illusion of safety that did in fact not exist. For these reasons, a blanket strong thread-safety guarantee for mutating shared objects is not provided, and constraints are put on programs accordingly.
The proposed wording talks in terms of data races and expression evaluation conflicts. Data races and expression evaluation conflicts are defined in the core language portion of the standard, so do not need to be further described in the library clauses.
Consideration was given to specifying rand function and the global locale objects on a per-thread basis. That is not required because it does not represent existing practice. Mac OS X, for example, does not support per-thread global locale objects.
As far as is known, the proposed wording reflects existing practice in current implementations of the standard library.
Add a new paragraph after 17.4 Library-wide requirements [requirements] paragraph 1:
Requirements specified in terms of interactions between threads do not apply to programs having only a single thread of execution.
Change 17.4.4 Conforming implementations [conforming] as indicated:
This subclause describes the constraints upon, and latitude of, implementations of the C++ Standard library. The following subclauses describe an implementationís use of headers (126.96.36.199), macros (188.8.131.52), global functions (184.108.40.206), member functions (220.127.116.11),
reentrancydata race avoidance (18.104.22.168), access specifiers (22.214.171.124), class derivation (126.96.36.199), and exceptions (188.8.131.52).
Change 184.108.40.206 Reentrancy [reentrancy] from:
220.127.116.11 Reentrancy [reentrancy]
Which of the functions in the C++ Standard Library are not reentrant subroutines is implementation-defined.
18.104.22.168 Data race avoidance [res.on.data.races]
This subclause specifies requirements implementations shall meet to prevent data races ([intro.multithread]). Each requirement applies to all standard library functions unless otherwise specified. Implementations are permitted but not required to prevent data races in cases other than those specified below.
C++ Standard Library functions shall be reentrant subroutines.
A C++ Standard library function shall not directly or indirectly access objects ([intro.multithread]) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's arguments, including
A C++ Standard library function shall not directly or indirectly modify objects ([intro.multithread]) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's non-const arguments, including
[Note: This means, for example, that implementations can't use a static object for internal purposes without synchronization because it could cause a data race even in programs that do not explicitly share objects between threads. --end note]
Implementations are permitted to share their own internal objects between threads if the objects are not visible to users and are protected against data races.
Unless otherwise specified, C++ Standard Library functions shall perform all operations with effects visible ([intro.multithread]) to users solely within the current thread.
[Note: This allows implementations to parallelize operations if there are no visible side effects. --end note]
Somewhere in [constraints] add a constraint on programs:
It is undefined behavior if calls to standard library functions from different threads:
- share access to an object directly or indirectly via their arguments, including
- at least one of the arguments accessing a shared object is non-const, and
- one call does not happen before the other ([intro.multithread]).
[Note: This prohibition against concurrent non-const access means that modifying an object of a standard library type shared between threads without using a locking mechanism may result in a data race. --end note]
To 18.5.1 Storage allocation and deallocation [new.delete], add:
The library versions of operator
delete, user replacement versions of global replacement operator
delete, and the Standard C library functions
freeshall not introduce data races ([intro.multithread]) as a result of concurrent calls from different threads. Calls to these functions that allocate or deallocate a particular unit of storage shall occur in a single total order, and each such deallocation call shall happen before the next allocation (if any) in this order.
To 19.3 Error numbers [errno] paragraph 1, add:
errnovalue shall be provided for each thread.
To 20.6.1 The default allocator [default.allocator], add:
Except for the destructor, member functions of the default allocator shall not introduce data races ([intro.multithread]) as a result of concurrent calls to default allocator object's member functions from different threads. Calls to these functions that allocate or deallocate a particular unit of storage shall occur in a single total order, and each such deallocation call shall happen before the next allocation (if any) in this order.
To 20.7 Date and Time [date.time], add:
localtimeare not required to avoid data races ([res.on.data.races]).
To 21.4 Null-terminated sequence utilities [c.strings], add:
strtokare not required to avoid data races ([res.on.data.races]).
To 22.1.1 Class locale [locale], add a new paragraph at the end:
Whether there is one global locale object for the entire program or one global locale object per thread is implementation defined. Implementations are encouraged but not required to provide one global locale object per thread. If there is a single global locale object for the entire program, implementations are not required to avoid data races on it ([res.on.data.races]).
At a location in 23 to be determined by the project editor, add a new paragraph:
For purposes of avoiding data races ([res.on.data.races]), implementations shall consider the following functions to be const:
equal_range, and, except in associative containers,
Notwithstanding ([res.on.data.races]), implementations are required to avoid data races when the contents of the contained object in different elements in the same sequence are modified concurrently.
[Example: For a
vector<int> xwith a size greater than one,
x = 5and
*x.begin() = 10can be executed concurrently without a data race, but
x = 5and
*x.begin() = 10executed concurrently may result in a data race. -- end example]
Change 26.7 C Library [c.math] paragraph 6 as indicated:
randfunction has the semantics specified in the C standard, except that the implementation may specify that particular library functions may call
rand. It is implementation defined whether or not the
randfunction may introduce data races ([res.on.thread.safety]).
[Note: The random number generation ([rand]) facilities in this standard are often preferable to
rand. --end note]
N2669 - Revision 2:
N2410 - Revision 1:
N2298 - Initial version.
N2429, Concurrency memory model (final revision), Clark Nelson and Hans-J. Boehm
N2480, A Less Formal Explanation of the Proposed C++ Concurrency Memory Model, Hans-J. Boehm
N2519, Library thread-safety from a user's point of view, with wording, Jeffrey Yasskin
N1947, The Memory Model and the C++ Library, Non-Memory Actions etc., Nick Maclaren