The Go 1.1 scheduler
morsmachine.dk
morsmachine.dk
https://code.google.com/p/go/issues/detail?id=543
Also, from the article:
> Go garbage collector requires that all threads are stopped when running a collection and that memory must be in a consistent state.
People often ask about how Go differs from Erlang. That one's a fairly large difference under the hood. Erlang does GC on a per process (Erlang process, not OS process) basis.
On our main codebase, we experienced some major issues when moving from Go 1.0 to 1.1 from this exact issue. We had a goroutine that was doing some remote calls wrapped with a timeout, and the call was consistently timing out even though the remote service was perfectly fine (it was another service on the same box).
We found the cause was another goroutine running something in an for loop that didn't do anything that would allow a pause in execution for another goroutine. So, the scheduler just obsessed on that goroutine, running none of the others until that one was done, and by then the timeout on the remote call had expired.
We fixed up that case, but also found a few others were we simply added a very small sleep call... for no other reason than allowing the scheduler to evaluate other goroutines. Meh. It made sense when we finally tracked it down, but was one of those things where we had to pause and ask "really?"... and adding a sleep call with comments "yes, I am really calling sleep".
But yeah, I would say that is one of the quirks of Go at this time. You definitely need to be aware of how the scheduler works if you're using it in production.
EDIT: Okay, saw your reply below. Just to reiterate, Erlang has to make a lot of throughput compromises to support pre-emptive multitasking. Just being compiled pretty much takes Go out of that conversation entirely. I'm happy with the tradeoffs for the kinds of things I need to do. And you can always set GOMAXPROCS to a _multiple_ of the number of cores on your machine to get OS-managed pre-empting.
But, I acknowledge that is only a slightly-better kludge. It's still not ideal.
edit: georgemcbay's runtime.Gosched() appears to be the "right" kludge, but I'm leaving this up just in case others weren't aware of the system call.
It was something that had always passed and never been an issue on 1.0.3, but started failing 85-90% of the time on Go 1.1 with no code changes. :(
Theoretically not since Erlang data structures are immutable, but outside of binaries which seat in a shared global heap the benefits was never considered to outweight the complexity costs (aliasing analysis and others), so messages are copied indeed.
The single "lock free" idlep looks like it's just moved the futex contention elsewhere. This will almost certainly bounce like crazy on a many-cored system. Would be interested to see benchmarks of the new design before considering it somehow better.
Possibly. The Go 1.1 scheduler is inspired by Java's fork/join scheduler, which suffered from the same problem in Java 7. In Java 8 it's been improved to no longer have a single wait-list, and external submissions of tasks (i.e. tasks that are not submitted by tasks running in the thread pool, but elsewhere) are multiplexed randomly (IIRC) among the individual thread queues.
The problem will show up when people try to fire up millions of goroutines and then wonder "why is my latency suddenly spiking in to the seconds! WTF!"
I'd love to see a pros/cons comparison between Go's all at once strategy vs Erlang's per process strategy.
Go's advantage is that you can share data cheaply while retaining memory safety [1]. The disadvantage is that you have stop-the-world GC and potential for data races, so you must rely on the race detector. Erlang's advantage is that you have no data races and no stop-the-world GC (and Erlang's GC is easier to implement). The disadvantage is that all messages must be copied and parallel algorithms that require data sharing are more difficult to write.
There are hybrid approaches like Singularity, JS with transferable data structures, and Rust (disclaimer: I work on Rust). These systems use some form of static or dynamic access control scheme (for example, uniqueness or immutability) to control data races and perform memory management for shared data structures, while retaining Erlang's thread-local GC.
[1] With one exception, the memory unsafe data race in maps and slices. See: http://research.swtch.com/gorace
* Fault tolerance
* Concurrency
From fault tolerance comes isolation. Don't let a part of your program that crashes affect or crash other unrelated parts of your program. Memory heaps are private for each actor (+/- some refcouting for binaries).
Hot code reloading comes from fault tolerance as well. So do immutable data structures and functional aspects.
As for concurrency. Erlang emphasizes "liveliness" and low reduction over throughput. This is quite rare and is very interesting. It means under concurrent load, it still tries to be responsive. So if 100k clients are connected, and on is performing a CPU intensive job, the other ones shouldn't get socket errors or get blocked. This might come with a trade-off of slowing down that one CPU bound function with frequent interrupts.
Here is a good article on how Erlang's scheduler works:
http://jlouisramblings.blogspot.com/2013/01/how-erlang-does-...
Now Erlang is a tool and it there is no free lunch. All these features you saw above don't come for free. Erlang will be slower in numeric and sequential computational tasks (the language shootout type benchmarks, like finding the shortest path, computer determinants and so on). So in some cases it won't be the answer. You'll have to benchmark and decide for yourself.
[0] http://dlang.org/phobos/std_concurrency.html [1] http://dlang.org/phobos/core_thread.html#.Fiber
"Once a context has run a goroutine until a scheduling point, it pops a goroutine off its runqueue, sets stack and instruction pointer and begins running the goroutine."
Do they mean: "begins running the next goroutine"?