4 ms·
Rust definitely allows data sharing. It just doesn't allow data races. > If you define data races to not include race conditions that your code is handling (b
by Jweb_Guru 12y ago
Rust definitely allows data sharing. It just doesn't allow data races.
> If you define data races to not include race conditions that your code is handling (by placement of atomics, fences, locking etc.) then maybe your points make some sense but it is exactly the code handling these that I am saying does not fit with what I have seen rust advocates claim.
Its actual guarantees for atomics are not very strong and let you handle all of those things in safe code. What's interesting is that it can guarantee correct behavior for types that have more interesting semantics than "a bytestring," like mutexes or smart pointers. The implementations of those things are still unsafe, but you can generally provide a safe API to them, which you can't do in most other languages. This is because Rust has the notion of thread safety built into the language and enforced by the type system. For example, Rust's `shared_ptr` equivalent, `Arc<T>` is defined to be thread safe to share and send to other threads if and only if `T` is also thread safe to share and send to other threads. That doesn't mean you can't build up a safe type from the lower level atomics, or Rust isn't suited for this domain. It means you can write safe APIs in Rust that you can't in C++.
> You have to reason about these things at some level, and what I hear people advocate on hn is to sidestep it all by preventing sharing, which may be a fine idea but doesn't work for everything.
Rust just forces you to do just enough synchronization to not have undefined behavior. It's not a dogmatic language and doesn't believe there is one right way to do concurrency. Channels were just removed from the prelude for precisely that reason--they're not particularly favored over other concurrency mechanisms.
> Lastly, in my mind, if your lock free algorithm works by letting the races happen but using the result of an atomic op and/or fences to ensure consistent behavior, I would still call that a (benign) race. That you needed atomics, fences, or compiler hints to get what you want seems orthogonal.
Data races are well-defined, and what you're describing aren't data races, benign or otherwise. You basically never want data races, just like you basically never to dereference a dangling pointer. Rust prevents things you basically never want, it doesn't prevent writing useful things like lock-free data structures.
- asveikau 12y ago> Data races are well-defined, and what you're describing aren't data races, benign or otherwise. You basically never want data races, just like you basically never to dereference a dangling pointer Lock free algorithms, or even a lock implementation, does not jive with this. You let the race happen, and you safely detect when you lost or won the race, and then you do stuff accordingly. To use your pointer analogy, it's OK to have a stale pointer in RAM or in a register at a moment in time. It's a violation of the invariants to dereference it. As for the rest, perhaps I have been misinformed or led to some outdated info on rust when I saw things about no sharing. I will be sure to take a look.
- dbaupp 12y agoLock free algorithms have to be careful to avoid dataraces by using the appropriate atomic instructions on the CPU. Using non-atomic instructions is a data race and may mean you incorrectly detect that you won the race (seeing stale data in a CPU cache or somesuch). A data race is essentially defined as a race condition between reads and writes where at least one of those is non-atomic. Rust's type system allows one to enforce atomicity by default (e.g. using atomic instructions, or wrapping data in a mutex).
- asveikau 12y agoAre you assuming that I haven't written these things in real life or something? I said "safely" detect, so yes, the final observation that you have won or lost the race must come from an atomic op. However, a "data race" susceptible read can totally be part of the process. The most common idiom I have seen for a lock free atomic read-modify-write has been to do an "unsafe" read, then act like it was OK, then issue compare-and-swap to determine if it really was OK and do the write if successful. (A failed compare and swap means you need to re-fetch and try again.) Yes you need to make sure fences are OK and the compiler is not caching/reordering the read and you need to account for the famous "ABA problem", blah blah blah, but it's what people do and I am not making it up for the purposes of an HN discussion. The typical RISC approach to atomics, load-link/store-conditional, also encourages what I will call "my kind of thinking" on this issue, though it does so with specialized instructions. You do a special read, then you use ordinary non-atomic register operations, then you do the special store which fails when you lose the race. I will give you that these are specialized instructions and not ordinary C assignments but I would suggest looking into them if you have not already, the semantics are very educational. (And they may just convince you that disallowing data races is an overly restrictive thing to do in some scenarios.)
- dbaupp 12y agoI was assuming you were talking about the race & detection as e.g. // let the "race" happen: old = compare_and_swap(some_shared_memory, 1); if old == 0 { // we won! } This is perfectly valid data-race-free code if the CAS is atomic, but is invalid if the CAS is not (assuming no other memory fences). But sure, if you're manually fencing then you can have data races that are benign; however, just because something has some relatively rare uses doesn't mean it's wildly unsafe for the general case, and so disallowing by default it helps the correctness of the vast majority of concurrent code. In any case, Rust allows one to opt-in to that sort of behaviour using `unsafe` locally, e.g. one would use `unsafe` deep in the internals of the implementation of the lock-free data structure and with careful vetting to ensure it's correct, and then all users can benefit with the compiler ensuring concurrency-safety by default. Rust tries not to completely disallow behaviour, just make memory safety the default. The programmer can override the compiler via `unsafe` if they truly know better.