3 ms·
The actor model (share-nothing) is one way to address the problem of shared, mutable state. But what if I want to have my cake and eat it too? What if I want
by mrkeen 2y ago
The actor model (share-nothing) is one way to address the problem of shared, mutable state.
But what if I want to have my cake and eat it too? What if I want to have thread-safe, shared, mutable state. Is it not conceivable that there's a better approach than share-nothing?
- moffkalast 2y agoAs much as I hate to say it, I think Java probably has the best eaten cake implementation by far. Volatile makes sure variables stay sane on the memory side, if you only write to a variable from one thread and read it from others, then it just sort of magically works? Plus the executors to handle thread reuse for async tasks. I assume C# has the same concepts given that it's just a carbon copy with title case naming. Python can't execute in on two cores at once, so it functionally has no multithreading, JS can share data between threads, but must convert it all to string because pointless performance penalties are great to have. Golang has that weird FIFO channel thing (probably sockets in disguise for these last two). C/C++ has a segfault.
- mrkeen 2y ago> Volatile makes sure variables stay sane on the memory side This doesn't get you from shared-mutable-hell to shared-mutable-safe, it gets you from shared-mutable-relaxedmemorymodel-hell to shared-mutable-hell. It's the kind of hell you don't come across until you start being too smart for synchronisation primitives and start taking a stab at lockfree/lockless wizardry. > if you only write to a variable from one thread and read it from others, then it just sort of magically works I'm not necessarily convinced by that - but either way that's a huge blow to 'shared' if you are only allowed one writer. > Plus the executors to handle thread reuse for async tasks. What does this solve with regard to the shared-mutable problem? This is like "Erlang has BEAM to handle the actors" or something - so what?
- moffkalast 2y agoWell it doesn't get you there because shared-mutable-safe doesn't exist, at least I doubt it can without major tradeoffs. You either err on the side of complete safety with a system that is borderline unusable for anything practical, or you let people do whatever they want and let them deal with their problems once they actually have them. > either way that's a huge blow to 'shared' if you are only allowed one writer Yeah for full N thread reading and editing you'd need N vars per var which is annoying, but that kind of every-thread-is-main setup is something that is exceedingly rare. There's almost always a few fixed main ones and lots running specific tasks that don't really need to know about all the other ones.
- throwitaway1123 2y ago> JS can share data between threads, but must convert it all to string To be more precise, you can send data to web workers and worker threads by copying via the structured clone algorithm (unlike JSON this supports almost all data types), and you can also move certain transferable objects between threads which is a zero-copy (and therefore much faster) operation.
- moffkalast 2y agoAh yeah dataviews, but you still need to convert from json to those and that takes about as much overhead, plus they're much harder to deal with complexity-wise being annoying single type buffers and all. For any other language it would work better, but because JS mainly deals with data arriving from elsewhere it means it needs to be converted every single time instead of just maintaining a local copy for thread comms.
- throwitaway1123 2y ago> Ah yeah dataviews, but you still need to convert from json to those and that takes about as much overhead You don't necessarily need to have an intermediate JSON representation. Many of the built in APIs in Node and browsers return array buffers natively. For example: const buffer = await fetch('foo.wav').then(res => res.arrayBuffer()) new Worker('worker.js').postMessage(buffer, [buffer]) This completely transfers the buffer to the worker thread, after which it is detached (unusable from the sending side) [1][2]. [1] https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage#transfer https://developer.mozilla.org/en-US/docs/Web/API/Worker/post... [2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/detached https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- moffkalast 2y agoHmm, well this I have to try sometime, thanks for the heads up :)
- neonsunset 2y ago> I assume C# has the same concepts given that it's just a carbon copy with title case naming. Better not comment than look clueless. Moreover, this applies to the use of volatile keyword in Java as well.
- pjc50 2y agoC# has Interlocked https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked?view=net-8.0 https://learn.microsoft.com/en-us/dotnet/api/system.threadin... which is strictly better than volatile because it lets you write lockfree code that actually observes the memory semantics. "Volatile" specifies nothing about memory barrier semantics in either Java or C++, if I remember correctly?
- gpderetta 2y agostd::atomic is equivalent of volatile in C++. C and C++ volatile is for other memory mapped I/O.
- klabb3 2y agoYou can always share immutable data. So shared nothing is a bit strong choice of words imo. > But what if I want to have my cake and eat it too? What if I want to have thread-safe, shared, mutable state. No, it’s not possible. Shared mutable state invokes ancient evils that violate our way of thinking about traditional imperative programming. Let’s assume you have a magical compiler & CPU that solves safety and performance. You still have unsynchronized reads and writes on your shared state. This means a shared variable you just read can change anytime “under your feet”, so in the next line it may be different. It’s a race condition, but technically not a data race. The classical problem is if multiple threads increment the same counter, which requires a temporary variable. A magical runtime can make it safe, but it can’t make it correct, because it cannot read your mind. This unfortunately leaves you with a few options, that all violate our simple way of life in some manner: you can explicitly mark critical sections (where the world stands still for a short amount of time). Or you can send messages, which introduces new concepts and control flow constructs to your programming environment (which many languages do, but Erlang does perhaps the most seriously). Finally, you can switch to entirely different paradigms like reactive, dataflow, functional, etc, where the idea is the compiler or runtime parallelizes automatically. For instance, CSS or SQL engines. I like message passing for two reasons: (1) it is already a requirement for networked applications so you can reuse design patterns and even migrate between them easily and (2) it supports and encourages single ownership of data which has proven to work well in applications when complexity grows over time. OTOH, I am still using all of the above in my day to day. Hopefully in the future we will see clearer lines and fewer variations across languages and runtimes. It’s more complex than it needs to be, and we’re paying a large price for it.
- gpderetta 2y agoThere isn't anything inherently evil with mutable shared state. If you think about it, what is a database if not mutable shared memory? Concurrency control (or the lack of it) is the issue. But you can build concurrency control abstractions on top of shared memory. Also remember that shared memory and message passing are duals.
- mrkeen 2y ago