|Reply to:||Hans-J. Boehm|
LWG 1151 (US 63) points out that we had made only an incomplete attempt at dealing with threading issues throughout the library. This is an attempt to improve matters by addressing as many of the remaining issues as we are currently aware of. Based on experience with other languages, it seems likely that more issues will arise in this area. But it appears to be getting harder to find the holes. This relies on discussions with many people, including Lawrence Crowl, Howard Hinnant, Peter Dimov, Nick Maclaren, and Paul McKenney.
27.4p4 [iostreams.objects] currently specifies:
Concurrent access to a synchronized (220.127.116.11) standard iostream object’s formatted and unformatted input (18.104.22.168) and output (22.214.171.124) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [ Note: users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. -end note ]
This raises the question of whether I can use iostreams to synchronize threads. If I set X to 1 in thread 1, write to a stream f, and then read the written value, or one that depends on it in thread 2, is thread 2 guaranteed to see the write to X (or a later one)?
The above paragraph appears to apply only to the standard iostreams. Any other concurrent access already produces a data races, avoiding the question. It is unclear to me that there is any way, strictly within the standard, of reading a value that was written to a standard iostream by the same process. Hence I'm no longer convinced we need to say anything here.
If we do need to say something, I think we should be consistent with the "sequential consistency for data-race-free programs" rule. Anything that does not introduce a race should ensure the appropriate happens-before relationship unless weaker memory ordering is explicitly specified. Thus, in the above example case, thread 2 should be guaranteed to see the write to X.
A number of standard container member functions are treated as const for race detection, in spite of the fact that they are not const functions. The at() function was inadvertently omitted from this list. Edit 23.2.2p1 [container.requirements.dataraces] as follows:
For purposes of avoiding data races (126.96.36.199), implementations shall consider the following functions to be const: begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, and, except in associative containers, operator.
The current spec is not very clear what it means for an "atomic read-modify-write" operation to be "atomic". Clarification:
Before 29.3p11[atomics.order] add a paragraph:
Atomic read-modify-write operations shall always read the last value (in the modification order) written before the write associated with the read-modify-write operation.
The current draft is unclear when iterator operations may conflict with accesses to other iterators or to the underlying container.
Add the following paragraph after 188.8.131.52p5 [res.on.data.races]:
For iterators obtained from standard library containers and strings, iterator increment and decrement operators, and +, - +=, and -= operators, may accesses the underlying container, but shall not modify it. For istream_iterator and ostream_iterator, they may also modify the underlying streams.
There was a question about whether the race behavior of algorithms such as equal() is sufficiently described. Equal() appears to be adequately described, since it uses InputIterators, implying that, according to 184.108.40.206p5 [res.on.data.races] it is not allowed to update anything through the iterators. Unfortunately, many of the algorithms use ForwardIterators only for input, which means they are underspecified. An implementation currently could read and write back the objects referenced by the iterators, which is clearly unacceptable.
Add after 25.1p3 [algorithms.general]:
For purposes of determining the existence of data races, algorithms modify objects referenced through an iterator argument only when the specification requires such modification.
Howard Hinnant points out that this is not as clear as it should be without more context. I'm also not sure that it is sufficient to deal with something like nth_element(), which has a specification that already seems less clear than I would like about what is being modified.
This arose during discussion of LWG 1218. LWG 1221 is also related.
I think we all agree that constructors and destructors of all library objects, including synchronization objects like mutexes, are not protected against data races. It is the programmer's responsibility to ensure that construction happens before any other use, and any other use happens before destruction.
The one exception here seem to be the somewhat more liberal condition variable rules, which allow a wait to complete after a condition variable has been destroyed.
There seems to be some agreement that the standard mostly says all of this already. In particular, the object lifetime rules in 3.8 seem to essentially state this, assuming 3.8 is interpreted as referring to happens before (1.10) ordering. Unfortunately, that's not as clear as it should be. I suggest adding the following at the beginning of 3.8:
All statements about the ordering of evaluations in this section, using words like "before", "after", and "during", refer to the happens before order defined in 1.10[intro.multithread]. [Note: We ignore situations in which evaluations are unordered by happens before, since these require a data race (1.10)[intro.multithread], which already results in undefined behavior --end note]
It would also help to add the following to as a second paragraph to 220.127.116.11 [res.on.objects]:
[Note: In particular, the program shall ensure that completion of the constructor for any library-defined class happens before any other member function invocation on that class object, and unless otherwise specified, the program shall ensure that completion of any member function invocation, other than destruction, on a class object happens before destruction of that object. This applies even to objects, such as mutexes, intended for thread synchronization. -- end note]
I believe condition variables are the only intended exception to this. However, the current precondition, as modified by LWG 1221, does not make this clear. It states "There shall be no thread blocked on *this", which is entirely redundant with the default lifetime rules, which would require that any wait calls happen before destruction. I suggest adding after 30.5.1p4 [thread.condition.condvar], whether or not updated by LWG1221, and after 30.5.2p3:
This relaxes the usual rules, which would have required all wait calls to happen before destruction. Only the notifications to unblock the wait must happen before destruction.