3 ms·
Shared state is bad because it breaks local reasoning. Without enforcing certain disciplines (i.e. always sharing by passing messages via Go channels) it's hard
by riobard 12y ago
Shared state is bad because it breaks local reasoning. Without enforcing certain disciplines (i.e. always sharing by passing messages via Go channels) it's hard, if not impossible, to reason about local behaviors without considering other parts of the code. Immutable vs shared mutable state is a design choice. Erlang chose immutability for safety, while Go chose shared mutable state for practical reasons, but as a remedy Go recommends best practices to avoid the drawbacks caused by it.
Pre-emptive scheduling is bad because you can have a goroutine running a tight loop starves other goroutines. To address that problem, you should manually call runtime.Gosched() to yield control and allow the scheduler to run other goroutines. Erlang's reduction-based scheduling does not have this problem and can be very fair.
Goroutine lacking identity is a major design difference from Erlang. In Go, channels have identity, but goroutines don't. In Erlang, it's the other way around: processes have identity, and channels are implicit a.l.a mailboxes. In theory you can simulate one style in the other, but the implication of this design choice is very proud. I'm a Go fan, but personally I think Erlang's model is easier to reason about in scale, and it has this nice symmetry with OS threads/processes (you can kill them easily. Good luck killing a goroutine).
- cnbuff410 12y ago> Erlang chose immutability for safety, while Go chose shared mutable state for practical reasons, but as a remedy Go recommends best practices to avoid the drawbacks caused by it. That's my point. I didn't say it clearly before. I understand why shared state is bad in general. I don't understand if channel takes care of most scenarios why it's still one of biggest problem for Go listed by author. > Erlang's reduction-based scheduling does not have this problem and can be very fair. What's the key difference between Erlang's reduction based scheduling and cooperative scheduling?
- riobard 12y agoIf you only share state by passing messages you should be fine. The challenge is to make sure the "only" part. Erlang's reduction-based scheduling basically counts how many steps each process has executed, and automatically switches to other processes once a process has finished a certain number of reductions. So even if a process is running tight loops, it would not starve other processes. Go's scheduler currently cannot promise that.
- davidw 12y agoAnother way to put it: you can write the equivalent of while (true) { do some stuff } in Erlang and it will not hang the system.
- derefr 12y ago> What's the key difference between Erlang's reduction based scheduling and cooperative scheduling? Cooperative scheduling is like reference-counting: you have to tell the compiler when it's safe to context-switch. Preemptive scheduling is like garbage-collection: the runtime decides on its own when it will switch, and you have to deal with making that safe. Reduction-based scheduling is a hybrid approach, and is, in this analogy, a bit like Objective-C's Automatic Reference Counting. Under reduction-based scheduling, context switches are a possible side-effect of exactly one operation: function calls/returns. This means that it's very easy to reason about when context switches won't happen (so it's not hard to write atomic code), while not having to worry about making them happen yourself (because, in a language that defines looping in terms of recursion, all code-paths have a known-finite sequence of steps before either a function call or a return.)
- cnbuff410 12y agoVery clear. The reduction based scheduling certainly looks interesting. Thanks for your explanation!
- josevalim 12y agoThere are also a couple technical trade-offs that are imposed by choosing mutable shared-state. For example, if Erlang processes had shared state, killing a process could get much trickier because you could potentially kill a process while it is changing the shared state, leaving it in a corrupted state. The language would have to work around this by providing atomic blocks or operations to guarantee such won't happen. At the end of the day this would mean more rules the developer needs to keep in mind to write safe software. Another example are tools like [Concuerror](http://concuerror.com/ http://concuerror.com/). It is an Erlang tool that executes your code and finds concurrency bugs and race conditions deterministically. The tool works by identifying all parts where there is communication and shared state in between processes and instrument them. After instrumenting them, it executes the code considering all possible interleavings, which ends up finding the bugs deterministically. I have been to a workshop and the tool runs extremely fast and its codebase is quite tiny (~2000LOC) because communication and shared state in Erlang exists only on few places which helps reduce the number of interleavings and make instrumentation straight-forward. However, if you have shared state, every mutation could possibly be mutating a state that is shared in between goroutines, so the places where you'd need to instrument end up being too many, which would generate too many interleavings. You could alternatively try to reject those code paths during instrumentation, at the cost of making instrumentation much more expensive.
- jfindley 12y agoIt's worth pointing out that there is an upside of shared state, in that goroutines are extremely cheap to create. There's also a fairly decent race detector[1], and clear documentation on what is and isn't thread safe. 1: http://blog.golang.org/race-detector http://blog.golang.org/race-detector
- samth 12y agoThe cheapness of goroutines (which is a good thing indeed) has nothing to do with shared state. Erlang processes are even cheaper.