7 ms·
Everyone seems to have their own definition of data race! The article uses the one from the Rustonomicon, and by that definition, the Python example does appear
by verdagon 5y ago
Everyone seems to have their own definition of data race! The article uses the one from the Rustonomicon, and by that definition, the Python example does appear to have a data race.
(Edit: Apologies for misreading; our definitions are indeed similar, continued below)
- Kranar 5y agoThe definition I gave is exactly the one given by Rust [1], by C++ [2], by Java [3] and the one found on Wikipedia [4]. In safe Rust data races are not possible and yet it is still possible for a Rust program to exhibit the behavior in your article, because your article is not an example of a data race but rather a race condition. Note that in the source you reference, it explicitly states that while data races are protected, race conditions are not. The scenario I gave, where performing an operation such as "x = x + 1" can result in x decreasing in value can only be caused by an overflow, or a data race. No interpretation of a Python or safe Rust program will ever produce a decreasing value of x when evaluating "x = x + 1" (ignoring Rust's overflow behavior) since data races are not possible. In Java or C++, since data races are possible it is also possible that "x = x + 1" produces a decreasing value of x (that is not due to overflow). Finally here is a good article that lays out the differences between data races and race conditions: https://blog.regehr.org/archives/490 https://blog.regehr.org/archives/490 [1] https://doc.rust-lang.org/nomicon/races.html https://doc.rust-lang.org/nomicon/races.html [2] https://docs.oracle.com/cd/E19205-01/820-0619/geojs/index.html https://docs.oracle.com/cd/E19205-01/820-0619/geojs/index.ht... [3] https://en.cppreference.com/w/cpp/language/memory_model https://en.cppreference.com/w/cpp/language/memory_model [4] https://en.wikipedia.org/wiki/Race_condition#Data_race https://en.wikipedia.org/wiki/Race_condition#Data_race
- verdagon 5y agoThanks for the clarification. It's interesting that we see the same definition so differently. The Python example has two concurrent accesses of the same memory (the global counter) where one of them is a write. That seems to meet the definition. We're also seeing the telltale symptoms of a data race. Note that we don't need to see slicing to qualify as a data race; as plenty cache-line-aligned data races don't exhibit slicing (not implying you were claiming this, just mentioning it for anyone curious). The Rustonomicon definition also adds "they are unsynchronized", perhaps that's the part we disagree on. I see this code as unsynchronized; there is no mutex or atomic increment happening around this operation. One could reasonably disagree, depending on what level they're looking at: statement-level, instruction-level, opcode-level, assembly level, uop level, or a higher "logical" level. Either way, the point of the article is to help debunk a common misperception that the GIL helps users with their concurrency in some way.
- Kranar 5y agoThe GIL helps users protect against data races, it does not protect against race conditions. In Python, due to the GIL, it is not possible to concurrently access a memory location and hence there is no concurrent access to x. There are concurrent threads of execution, but they are interrupted either before accessing x, or after accessing x. At no point is a thread of execution interrupted right in the middle of an access to x.
- verdagon 5y agoThese things are running concurrently with each other though. Concurrent operations can happen one after the other, and Python's threads are more about concurrency than parallelism. If these definitions instead mentioned "simultaneous" or "in parallel", I could agree with your belief. (Edited in response to your edit)
- Kranar 5y agoThere need not be any parallelism or simultaneity for a data race to occur. What matters is that writes occur concurrently with another read/write, not whether they occur in parallel. For example it's possible on a single core CPU to have one thread operate on the higher bits of a 32-bit memory location then yield to second thread that operates on the lower 32-bits of the same memory location so that the end result is a clobbered write. At no point do the two threads run simultaneously and yet this would still be an example of a data race. Python's GIL makes such an access impossible on any platform it runs on.
- mirashii 5y ago> The Python example has two concurrent accesses of the same memory (the global counter) where one of them is a write. That seems to meet the definition. Ah, but no, there is never concurrent access to that memory address, that's what the GIL prevents. The loads/stores to that memory address happen only while the GIL is held. The "concurrency" being used here is much more narrow than the dictionary definition of concurrency and the way it is usually discussed in the Python world.
- hedora 5y agoI don't think that program can cause the value to decrease below its initial value with any current C compiler on any modern architecture. (Ignoring overflow, and also cases where the integer is misaligned. Misaligned accesses are a bus error on some architectures, so it's so far into undefined behavior that the race isn't very interesting in my book.) I'd be interested to see a concrete example of how it could actually happen on a real currently-deployed system. (I'm not saying people should normally write such code, but things do need to ground out in a concrete machine model at some point, even if it's just for the sake of implementing higher level language semantics.) (Edit: replied to you with similar comment in sibling thread...)