4 ms·
Yes, like the amount of thinking and novel work Rich Hickey out into the immutable but fast data structures in Clojure, the use of mvcc stm for transactions whe
by mapgrep 6y ago
Yes, like the amount of thinking and novel work Rich Hickey out into the immutable but fast data structures in Clojure, the use of mvcc stm for transactions when you do need to share mutable state between threads - it was there from the start. I don’t even pretend to fully understand half of the things going on in Clojure concurrency on a deep level but I find it reassuring that parallelism was deeply considered from the start. And also grew with even deeper models like core.async.
How many languages are there like this? A handful? Erlang, Scala, Haskell, Go.... Java has always been parallel but with pretty primitive concurrency tools, no?
- cutler 6y agoDon't forget Kotlin's coroutines.
- pjmlp 6y agoJava started with the same concurrency primitives as Ada and Modula-3. Java 5 got the concurrent package, with support for futures, chained computations, ability to create ou own schedulers, only matched by .NET dataflows. Java 8, integrated the concurrent package with parallel streams. Recent versions are being refactored so that eventually virtual threads are surfaced across all runtime, so instead of not knowing if green or red threads are being used like in the early Java days, the developers get to choose which ones to use. On .NET side, naturally it copied the Java primitives, then came TPL, Dataflow, async/await, PLINQ, F# agents, F# async/await (not the same as standard async/await, based on F# computations).Additionally there is the research done with Axum, Cω, Singularity, Midori, Immutable C#.
- julik 6y agoIn Go pretty much everything is very much mutable. The part that distinguishes Go is exactly the M:N scheduler used for both evented IO and parallelisation of compute. For _concurrency_ (what the Ruby fiber schedulers are for) what is proposed is probably quite a good idea. We can do as much hatin' as we want on JavaScript but its concurrency story has been much more successful than Ruby's due to evented IO as well - even though everything is mutable. As far as Ractor is concerned - well, WebWorkers are kind of similar - they get a separate thread, but you can only send them "messages" which must be immutable. How the MRI team is going to tackle mutations in those messages is an interesting excercise, because Ruby is ultimately a _live runtime_ - i.e. most of the things can change at any time. Not because it is made for sloppy programming but because it is made for incremental changes to the VM like Smalltalk.
- derefr 6y ago> How the MRI team is going to tackle mutations in those messages Object.freeze, presumably. Immutability exists as an option within Ruby’s OOP system, and has since the beginning, so it’s well-integrated[1], with other language features having been built to take it into consideration. I would expect any[2] message sent to a Ractor to be 1. On the sending side, verified as being acyclic; 2. On the receiving side, reconstructed by doing a recursive “clone and then freeze” operation. Since most messages wouldn’t be “deep”, this wouldn’t usually be costly; but in the case of a “deep”(ly hierarchical) message, you could probably pre-“deep-clone-and-freeze” the objects you’re going to send, and the runtime would hopefully detect this and not bother to deep-clone-and-freeze them again. (Maybe they’d add a runtime tag-bit on objects for “me and all my references — transitively! — are frozen”, that could be pre-set for any object when it’s frozen, if it sees that all its instance-variables are also already marked as transitively-frozen.) Alternately, they might just lean on the Marshal module, adding the capability for Marshal to load a dumped object-graph as all-frozen; and then just use Marshal.dump on the sending side and Marshal.load(..., freeze: true) on the receiving side. This wouldn’t allow for the efficiency wins of the above, but it would have the advantage of being a single already-well-tested-and-optimized all-in-C transformation; and it would give the MRI maintainers some docs to point at (those of Marshal.dump) to indicate what’s possible to send in a Ractor message. ————— [1] Integers, Floats, and Symbols come frozen by default; and there’s a pragma you can add to source files such that their string-literals will also be created frozen. So it’s not like Ruby code isn’t “prepared” for frozen objects. Your own Ruby code deals with plenty of them! [2] What if you want a mutable reference to an object in another thread? Well, think about the semantics of what you want. Presumably, you want sending a message to the object to actually first send (a recursive frozen clone) of your message back to the object’s owner-Ractor to handle. What system already does this? DRb! You can send a DRb object-proxy handle to another Ractor, and it won’t matter that it receives a recursively-frozen clone of it; it’ll still work to communicate with your own Ractor’s mutable “remote” object. Think of DRb handles as the Erlang PID-objects of Ruby.
- julik 6y agoIndeed, but I don't see as much issue with things which are freeze-able. You can Marshal.dump cyclic structures already with no issue. What seems to be the culprit at the moment is changing the method definitions / module composition of modules which participate in exchanges between Ractors. A lot of Ruby modules define constants dynamically, prepend modules to patch missing or broken functionality on existing modules, modify constants to patch bugs. Since there is no compile step / monomorphisation ahead of time it is likely that a program will be evolving after the VM has started and all the code has been loaded. I do not see the Ractor messaging setup covering it well at the moment. Maybe Ractor messages should be their own type which is only allowed to contain marshalable objects, and inside a Ractor the VM must be able to "replay" the same code structure mutations as the the entire VM (or those mutations must be cloned into the Ractor where appropriate). While a lot of people scream that "you should not monkeypatch" (and it is pretty much always a good idea not to) it is not always practical, given the fact that a lot of software produced is imperfect and does need careful patching sometimes, and Ruby is great at allowing it.
- colonwqbang 6y agoLanguages like Haskell, Clojure, Erlang provide good "modern" abstractions like STM which are great. But even having the basic threading primitives built in from a early point would help I think (e.g. Java, C++). Why? Because it does not let library authors entertain the idea that they can just ignore all parallelism concerns.