Hacker News new | past | comments | ask | show | jobs | submit login

It's always been a better sandbox model, but message passing is still a pain to orchestrate compared to shared-memory data structures.



Personally I prefer message passing (over pipes).

Shared memory (be it shm/heap in same process) with associated mutexes, semaphores, locks and the like is a right pain to get right without introducing race conditions, deadlocks etc.

Go is interesting as it uses "micro threads" (goroutines) and message passing CSP style but I haven't found a use for it yet.


Going multiprocess and using IPC doesn't intrinsically eliminate any of the tricky concurrency challenges with multi-threading. Neither do coroutines (goroutines).

When you have multiple processes talking to another then, unless all your RPCs are completely stateless, you still need to orchestrate synchronisation in some form. There's also the problem that in 2013 we still don't really know how to do IPC/RPCs really well, and doing so portably is hard (Notice Mozilla are writing their own from scratch)

It's all very well new languages coming along and solving the trivial stuff (setting up message queues, isolating memory) but very few of them do anything to solve the real problems.


> It's all very well new languages coming along and solving the trivial stuff (setting up message queues, isolating memory) but very few of them do anything to solve the real problems.

I think that Rust makes a lot of effort to move things in the right direction. In Rust you can share memory, but unless you are in the unsafe sublanguage (which is clearly marked) the type system restricts you to one of: (1) copying messages; (2) transferring ownership of a message so that the original thread cannot race on it; (3) sharing immutable memory only; (4) taking a lock before accessing mutable data. We've seen huge engineering benefits from this: making parts of Servo thread-safe has been simply a matter of running the compiler repeatedly and letting the error messages tell us where to insert the locks; when it compiles we know the data races are gone. (You can still deadlock though.)


True, but it does help making the applications more resilient specially if they are stateless.

The "let it crash" model from Erlang and micro-kernels.

Still you are right, we are far from having an acceptable solution that we could apply everywhere, so to say.


This is why Mozilla are investing heavily in Rust, so that they can use its semantics to share memory in a safe way.


Yes, I look forward to the day memory unsafe languages are only part of legacy systems.


> Go is interesting as it uses "micro threads" (goroutines) and message passing CSP style but I haven't found a use for it yet.

But is also has shared heaps by default. So you still have to rely on everyone being an "adult". I wish shared heaps would have been a specially enabled feature not the default. But I guess the language was position to compete with C++ and Java and isolated heaps would have provided a performance decrease in benchmarks -- and thus people's willingness to adopt it.

For large concurrent systems, safety and fault tolerance often leads to their failure but it is kind of hard to encode that in a quick benchmark to impress people.

Here are a few languages/systems with default isolated heap runtimes between concurrency units: Dart's isolates, Erlang's processes, Nimrod's threads, Web Workers in modern browsers. Anyone know of more?


"I wish shared heaps would have been a specially enabled feature not the default."

I hope to someday see the language at least do the opposite, have a specially-declarable "isolated" goroutine. In theory, the compiler ought to be able to analyze a goroutine, determine that it shares no data at startup with another process (i.e., nothing in a closure or something), determine that it only communicates via value-passing channels (which courtesy of my previous restriction, can be analyzed by simply looking at what channels are passed in at startup time, and some analysis of the types of the channels), and thus guarantee that at least this goroutine is fully isolated. Pervasive usage of the new keyword I'm hypothesizing would allow a diligent programmer to recover most of the isolation advantages without having to rewrite Go entirely. It also ought to enable some other optimizations against these guaranteed-isolated goroutines, the biggest of which is that they no longer need to participate in a global stop-the-world GC, both in that they can continue running while that is occurring and that they also relieve the global GC from the task of scanning over them.

(In fact all the analysis ought to itself be fully automateable, and the user shouldn't have to declare it; I'd want them to still have the option to make a declaration so the compiler can tell them if they screwed up, though. I don't like such critical functionality being behind an opaque optimizer.)

But this certainly won't happen soon; there's a large enough list of stuff that comes before that.


> determine that it only communicates via value-passing channels (which courtesy of my previous restriction, can be analyzed by simply looking at what channels are passed in at startup time, and some analysis of the types of the channels),

That would preclude sending interfaces over channels (along with any other existential or mutable reference type), because the type system doesn't know whether the interface is closing over shared state. Not being able to send interfaces over channels would mean that channels would be restricted to only one kind of type, because Go doesn't have discriminated unions so interfaces are the only way to perform type-switch. Those goroutines would be so restricted as to be almost useless.

You cannot just bolt isolation on after the fact. You must design your language for it from the start.

That said, Go's race detector is very good and it's awesome that they focused on getting first-class support for runtime race detection so early.


"You cannot just bolt isolation on after the fact. You must design your language for it from the start."

Yes, I agree, and I'm sure I'm going to have many years of wishing they had. At the moment I don't have a better entrant in this field that is palatable to my coworkers, though. They've rather disliked Erlang (and not for lack of trying, and not for lack of good reasons, for that matter), Haskell's right out, and I'm running low on production-quality true isolation-based languages here. Several additional up-and-coming contenders; I'm sure if I could have used Rust-from-2018 I'd take that in a heartbeat, but, alas, it's 2013.

Go is tolerable, at least the way we're using it.


> They've rather disliked Erlang (and not for lack of trying, and not for lack of good reasons, for that matter)

may you please elaborate on reasons for not using Erlang ?


Sketched: 1. As neat as the clustering is, it's very opaque and hard to debug. And even after years of using Erlang, it's always a pain to set it up again, and opaque when it fails. This is a critical feature for the system I've written, and I just can't keep it stable. That others seem to have managed doesn't help me any, and I'm done pouring time down this sinkhole. 2. The syntax is quite klunky. I've been programming in Erlang for 6 years now, including for my job, and yes, I still don't like the syntax. In addition to ",.;", it's a terribly klunky functional language, wearing a lot of the trappings while failing to reap a lot of the benefits. And I don't just mean this is a minor inconvenience, it seriously inhibits me from wanting to create well-factored code, because it's so much work I can't factor away. (I have examples, but explaining them is a blog post, not a 6-th level nested HN post) 3. As neat as OTP is (and it is neat), it tends to encourage a highly coupled programming approach to fit into "gen_server" (or whatever), and due to problem #2, many of the tools I'd use to solve that from either the imperative side or the FP side are not present, or too hard to use. The whole gen_X encourages very choppy and hard-to-follow code. If you pour enough work into it, you can get around that, but the language doesn't help you enough. It's also bizarrely hard to test the resulting OTP code considering we started with a "functional language".

It's a brilliant language that was well ahead of its time, and I don't mean that merely as a "I want to be nice" parting comment; it is a brilliant language that was ahead of its time and every serious language designer should study it until they deeply understand it. Indeed, I will absolutely attribute a significant portion of my success in programming Go to the wisdom (no sarcasm) I learned from Erlang, and Go would be a better language today had the designers spent more time learning about it first. (It still wouldn't be an Erlang clone, but it would be a better language.) But it's just become increasingly clear that it has been a drag on my project, for a whole host of little reasons that add up. It was the right decision at the time, because virtually nothing else could do what it did when I started, but that's not true anymore.

Someone will be tempted to post a point-by-point rebuttal. My pre-rebuttal is, I've been programming in it for six years (so, for instance, if there's some "magic solution" to clustering that has somehow escaped my six years of Googling, well, I think I did my part), yes, I know all other languages will also have "little things" (and big things), and Erlang may be perfect for your project, absolutely no sarcasm.


> Anyone know of more?

Rust's "tasks" feature isolated memory, and, thanks to the magic of linear types, passing data from one to another is both statically-guaranteed to be safe and is never more expensive than the cost of copying a pointer.


> is never more expensive than the cost of copying a pointer

Well, if you're passing something that is pointer size, yes. But say, `chan.send([0u8, .. 1_000_000])` will do at least one 1 MB memcpy to load that into the stream, and a 1 MB memcpy to load it out of it when `.recv()` is called.


>Here are a few languages/systems with default isolated heap runtimes between concurrency units: Dart's isolates, Erlang's processes, Nimrod's threads, Web Workers in modern browsers. Anyone know of more?

Tcl runs an interpreter per thread and communicate each other using message passing.


In D, by default global data is actually thread local. You need to explicitly enable sharing.


But you can send shared memory between threads and race on it, presumably?



My C code doesn't share heaps. Strict privsep design meaning one heap per process and no threads in favour of "select".


Well, OS processes are the obvious solution. I was asking about languages/runtimes that feature that as a default.

> and no threads in favour of "select".

Not sure why you mentioned that. Callback chains can create concurrency contexts (callback chains) that can interfere with each other, much like multiple threads would. It would have a much higher granularity but you are not out of the woods.


Only because I usually end up knocking up network servers.

That is true but you only have one thread to synchronise so mutexes and locks are vastly simplified.


On most modern operating system you can selectively set up shared memory between processes as well. One can implement a lower latency (but more dangerous) buffer sharing / message passing-by-reference scheme.


The biggest issue is performance. Although you might be able to work around it with local RPC calls and passing memory ownership as offered by Windows, Mac OS X, QNX and Minix. To quote the ones I am familiar with.

Just imagine the performance impact of something like Eclipse if each plugin was a separate process using message passing.

Maybe it is doable, not sure. Anyway I rather favor safety over performance.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: