12 ms·
A vision of a multi-threaded Emacs
- melony 4y agoNote that Clojure offers multiple concurrency systems, the default STM is not particularly performant and doesn't scale very well. You are better off calling into Project Loom for concurrent code.
- Thaxll 4y agoClojure is memory safe and no data races possible?
- melony 4y agoMemory safe, perhaps, but data races are definitely possible. The main issue is that their STM system doesn't scale very well. It is good enough if you want to keep a simple CRUD GUI in sync but don't expect to be building high performance web servers anytime soon with Clojure atomics. You can't just create and throw those around willy-nilly like Goroutines. Clojure people doesn't like talking about the language's weaknesses but I have seen quite a few Clojure projects get rewritten because of maintenance cost and runtime inefficiencies. Project Loom is the best option right now for scalable Clojure concurrency/parallelism.
- lilactown 4y agoSTM, atomics and channels are all different things that can be use to build different kinds of systems, so I think it's good to disambiguate them when talking about concurrency problems and their tradeoffs. Clojure' STM is basically unused. It's a suite of functions and a container type `ref` that allows you to do transactions in memory and keep everything consistent within the view of a transaction. The overhead required to do the book keeping for these transactions is often not worth it for most use cases in practice. Clojure also, as an alternative, gives you `atom` which is a type that implements a simple thread safe compare-and-swap. This ends up being sufficient for many cases of sharing state between threads. When combined with Clojure's immutable data structures, one can often use them as a simple in memory database. Those two things are about sharing memory, but has nothing to do with what the article talks about: green threads. clojure.core.async, as the article succinctly explains, allows you to create "goroutines" or green threads which are then multiplexed across some number of OS threads. This can allow you to create many more threads for computation than you have OS threads, and combined with thread locals and atoms can allow one to build large scale systems. Clojure's STM and atomics are sort of a non-sequitur from what the article is talking about Now as for downsides of Clojure, there are plenty of gotchas in practice that can lead people to issues with the three things above: * STM is slow and rarely useful compared to regular atomics * atoms CAS assumes you are using immutable data structures * core.async has a lot of sharp edges, including lack of first-class error handling and the fact that doing synchronous I/O on a go routine can end up starving the thread pool they are running on I've never built a high performance web server, but I imagine it's possible with Clojure; it would probably require you to probably eschew immutable data structures within the plumbing (for performance) and use an async I/O framework along with core.async to get competitive with a language like go. For shared memory, you probably would end up eschewing compare-and-swap for something more bare bones like locks, which you have access to via Java interop. At that point, you may wonder why you even chose Clojure in the first place instead of using Java directly. :D Project Loom really only helps solve the problem that core.async is trying to solve, and applications would still make use of atomics like `atom` and basic Java atomic values. My hope is that it makes it easier to do I/O, since it sounds like along with loom fibers the project is adding first class support for suspending and resuming on fundamental I/O operations. Time will tell.
- CJefferson 4y agoNote: Not at all an emacs expert, but know some stuff about trying to parallelise a long-standing language ( GAP -- www.gap-system.org , 30 year old maths language). I'd strongly recommend something like Javascript's "web workers", where each thread lives in an almost entirely independent memory space, with sharing limited to explicitly named buffers. The problem with traditional "everything shared by default" threading is even when you get the basic core working, we found throughout the system there were buffers and caches, which all needed making thread-safe. Even after that, every library needed some fixes -- and while these were often simple these libraries may not have had updates in years.
- admax88qqq 4y agoWebWorkers are not very useful as a result. The communication overhead of copying data between two memory spaces usually dwafs any apeedups you could get from doing the work in parallel. It's easier to implement as a language and runtime, but to get truly high performance on today's many thread many core machines you need shar d memory concurrency more like Java
- CJefferson 4y agoTrue, they are of limited usefulness, but in Emacs you could (probably) make them more useful over time by exposing more and more sharing -- but (I'm again guessing, don't know Emac's insides) it might be easier/safer to start sharing nothing, then work on sharing as much as possible, rather than start with full sharing and then try to make everything work.
- jlokier 4y ago> to get truly high performance on today's many thread many core machines you need shared memory concurrency more like Java JavaScript supports shared memory concurrency. Shared memory¹ is available in browser WebWorkers these days.² Atomic ops for building multi-threaded data structures³ are available too⁴. Including primitives for thread synchronisation⁵⁶ used by mutexes, condvars, producer-consumer queues, etc. ¹ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... ² https://caniuse.com/sharedarraybuffer https://caniuse.com/sharedarraybuffer ³ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... ⁴ https://caniuse.com/mdn-javascript_builtins_atomics https://caniuse.com/mdn-javascript_builtins_atomics ⁵ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... ⁶ https://caniuse.com/mdn-javascript_builtins_atomics_wait https://caniuse.com/mdn-javascript_builtins_atomics_wait
- Kaze404 4y agoI'd love to see this someday, as an Emacs user. One of my biggest frustrations with Emacs is how it can simply just hang while waiting for anything IO bound (like a response from LSP). It mostly only happens when opening a new file, while in VSCode for example the file is opened immediately and things like LSP and syntax highlighting are loaded in the background.
- metroholografix 4y agoThat multi-threading will solve this issue, is a common misconception. Emacs already supports asynchronous I/O and has sentinels for exactly that. However, not everything that runs on Emacs is taking advantage of that functionality. It is therefore up to you, to use the proper package (or not use the wrong one) / configure Emacs in such a way, so that this sort of issue is avoided. I can definitely say that if you're experiencing I/O hangs, it's either misconfiguration or bad code in a package that you loaded. Finally for LSP, I always recommend Eglot over LSP Mode, as Eglot is written by an Emacs contributor that has deep knowledge of Emacs and Emacs Lisp. LSP Mode is written by volunteers on Github, and the code is nowhere near as good. Given the (enormous amount / ever-increasing number) of Emacs Lisp packages out there, a lot of them being substandard, being able to make these sort of qualitative calls (and also increasingly dive into Emacs internals) will pay off. Assuming moderate effort, this will come with experience and time spent using Emacs.
- jonpalmisc 4y agoAnother vouch for Eglot here. Seems smoother and just nicer overall compared to lsp-mode, not to mention better integration with Emacs. I switched to Eglot a while back and haven’t looked back.
- Kaze404 4y agoWow, I had no idea! I'll give Eglot a try, thank you :)
- Athas 4y agoJoining the eglot choir. It behaves like I expect Emacs modes to behave and hooks into other Emacs functionality (xref, eldoc). Actually, it behaves better than that: it often works with zero configuration, which is much better than I expect.
- tgbugs 4y agoOne big challenge for multi-threaded Emacs is that there are many elisp libraries and common patterns around buffer modification that make implicit assumptions about sequential and exclusive execution. The various race conditions that would appear in a multithreaded context would be a nightmare to debug because they would likely only occur sporadically and would be hard to reproduce. I would love to be wrong about this, but I see it as a major obstacle that will likely need to be sandboxed (as was done with lexical scoping). For an example of how hard these kinds of issues could be to debug look no further than the incredible difficult that the undo-tree author had in debugging a race condition with the garbage collector. It took years. In the context of this piece, having a buffer mutex makes certain use cases for multi-threading impossible, for example dividing the buffer up into chunk equal to the number of cores and running a regexp over each chunk.
- ungamedplayer 4y ago> One big challenge for multi-threaded Emacs is that there are many elisp libraries and common patterns around buffer modification that make implicit assumptions about sequential and exclusive execution. It might be naive of me, but why not limit the process execution to the state and buffer that its working in ? I know that this does not answer the multi processor per buffer problem, but its a better start.
- rcthompson 4y agoCouldn't you implement things such that any number of readers can access a buffer concurrently, but modification requires exclusive access?
- agumonkey 4y agoqueues of compare-and-swap microtransactions
- gorgoiler 4y agoOne would have thought a purely functional piece of software written in Lisp was the poster child for parallel execution. Emacs clearly can’t easily do that, as per the article — the core of what it does is stateful — but what are some good examples where scaling horizontally with pure functions has worked?
- ehaliewicz2 4y agoemacs isn't pureful functional, and I don't think much lisp code ever was
- harperlee 4y agoExactly, lisp was always multiparadigm, and only more recently clojure has pushed forward the idea that being as purely functional as possible (with immutable data structures and pure functions) is something that works pretty well in a lisp.