4 ms·
Haskell is quite thread safe, I suppose it is at least as good as Rust in this respect. Immutability by default and garbage collection really help to cut away t
by colonwqbang 5y ago
Haskell is quite thread safe, I suppose it is at least as good as Rust in this respect. Immutability by default and garbage collection really help to cut away the ceremony around threading.
- tialaramex 5y ago> I suppose it is at least as good as Rust in this respect. (Safe) Rust has Data Race Freedom, which means you get Sequential Consistency. As I understand it, Haskell does not promise you data race freedom, so, if you mistakenly implemented a data race now you don't have Sequential Consistency and now you can't reason about non-trivial programs at all. I think Haskell, like Java, and unlike C or C++, promises you still get a meaningful program if you have a data race, but it may be tricky to debug the mess. Edited to expand: Suppose you're writing a program to process 16GB of data, the result will be a floating point value between 0 and 100.0 as a result of this processing. You realise it's embarrassingly parallel and so you spin up 16 threads to solve the problem in parallel on your CPU cores. Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads, the processing involves a lot of twiddling data, and so now it's full of data races. What is the result of your buggy program? In Rust, the program doesn't compile, or depending how you wrote it, exits immediately reporting that you can't go around processing the same data from different threads. In C or C++ maybe it deletes all your data, more likely it simply exits instantly with no result, spins forever, or outputs something completely spurious. In Java you might get an Exception reporting that you mustn't do this, but you might get a bogus but real looking answer like 46.2 now good luck figuring out why. I don't know for sure about Haskell, but I think you get a similar situation to Java minus the exception.
- pjmlp 5y agoOn the other hand, if one decides to use 16 processes to handle the data across a NUMA shared memory segment, to be able to easier scale it across the cluster, the Rust type system is of little help.
- tialaramex 5y agoTo make this work you're going to need an (unsafe) wrapper that lets all your processes get write access to the same 1GB of shared memory. If you write a wrapper that ensures safety, you'll get back to the same place where the faulty program reports that you can't all map the same 1GB of data and twiddle it, and if you don't it's on you for introducing unchecked unsafety.
- pjmlp 5y agoAssuming every single OS process that has access to the NUMA segment is written in Rust and originates from exactly the same build, which is impossible to represent on the type system.
- zozbot234 5y ago> (Safe) Rust has Data Race Freedom, which means you get Sequential Consistency. I don't think this is correct. Safe Rust includes atomics that are data-race free but need not be sequentially consistent.
- tialaramex 5y agoUnsurprisingly core::sync::atomic is not in fact implemented in Safe Rust. However you can indeed use it from Safe Rust, and you can choose Orderings which do not give you Sequential Consistency if you're comfortable with that, or more likely because you are implementing an algorithm whose authors assure you this is fine (I hope they're correct). I think that's a very modest caveat to the overall promise of Sequential Consistency, but sure, it is a caveat. If you write code that explicitly asks for Ordering::Relaxed and are astonished you don't get Ordering::SeqCst I guess I apologise for confusing you.
- juxtapose 5y ago> I think Haskell, like Java, and unlike C or C++, promises you still get a meaningful program if you have a data race This is true (when you insist on mutable shared memory), but in Haskell the safety guarantee is in fact not so far from Rust. The Haskell community is well aware of concurrency issues very early, and they developed a dozen of composable abstractions to solve these problems. (IMHO Rust is the only one that has a truly novel solution in recent years.) In Haskell, either you have to use `unsafe` functions to share bulk of memory or you don't share memory at all (as mutability is a pita in Haskell). On top of these unsafe functions, you can build other safe abstractions, just like Rust. These abstractions are often available as higher-order functions in libraries, making them highly composable and reusable. E.g., the canonical memory-sharing data type is `TVar`, which must be access from the `STM` monad [1], guaranteeing both atomicity and data race freedom. (BTW, STM stands for software transactional memory if you aren't familiar with it.) A major offender is `IORef`, and fortunately Haskell provides atomic operations on `IORef`s. GHC Haskell also offers free parallelism for all pure computations via the `Eval` monad [2]. > Mistakenly instead of processing 1GB per thread, you process the same 1GB on all sixteen threads This is highly unlikely in Haskell. Haskell emphasizes on composable combinators, so a program that does not share memory would probably look like foldr accumulateFunc initValue . mapConcurrently processBatch . split batchSize $ data (mapConcurrently here is a real function [3].) I'm sure C++, Java, and Rust have similar abstractions, but imperative languages make it too easy for programmers to do `for` loops, making it much more error-prone. Several array libraries provide safe parallelism. The interface is similar to Rust rayon, but the way they work differs greatly. For example, in Repa [4] you can do computeP $ traverse inputArray newShapeFn (\getElem curIdx -> ...) Here `traverse` results in a "delayed" array: the results are not immediately available, and what you are doing is just composing array operations. `computeP` forces the computation to happen _in parallel_. Since GHC is very capable of optimizing intermediate data structures away, this code can compile to very efficient machine code -- there is no mutability at all, yet you get very safe and efficient parallelism. PS: what Haskell is really bad at so far is safe in-place updates for arbitrary data types (like custom ADTs), which is solved by Rust cleanly. GHC 9.0 added Linear Types [5] to address this problem. Haskell and Rust are both bad at safe in-place updates of arrays, vectors, etc., and are likely to be bad for a long time -- their indices are arbitrary integers, which cannot be analyzed statically at compile time. Haskell probably can solve this problem completely when it gets dependent types [6], whereas Rust is unlikely to support DT any time soon, if ever [7]. [1] https://hackage.haskell.org/package/stm-2.5.0.1/docs/Control-Monad-STM.html https://hackage.haskell.org/package/stm-2.5.0.1/docs/Control... [2] https://hackage.haskell.org/package/parallel-3.2.2.0/docs/Control-Parallel.html https://hackage.haskell.org/package/parallel-3.2.2.0/docs/Co... [3] https://hackage.haskell.org/package/async-2.2.4/docs/Control-Concurrent-Async.html#v:mapConcurrently https://hackage.haskell.org/package/async-2.2.4/docs/Control... [4] https://hackage.haskell.org/package/repa https://hackage.haskell.org/package/repa [5] https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0111-linear-types.rst https://github.com/ghc-proposals/ghc-proposals/blob/master/p... [6] https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0378-dependent-type-design.rst https://github.com/ghc-proposals/ghc-proposals/blob/master/p... [7] https://github.com/rust-lang/rfcs/issues/1930 https://github.com/rust-lang/rfcs/issues/1930