So in case of most IO, Go handles multiple operation in a blocking synchronous way. But since goroutines use very light threads and its own scheduler, it doesn't matter how many goroutines it spawns, and it never becomes an issue. Am I understanding correctly?
评论:
McHoff:
sioa:Exactly correct -- if you're developing web-like stuff it's very refreshing to suddenly ignore the world of callbacks, promises, and other asynchronous garbage.
ChristophBerger:Yep, it really is refreshing.
sioa:It almost does not matter. A goroutine starts with a stack size between 2KB and 8KB (depending on the OS), which is tiny but can sum up.
ChristophBerger:That's why for networking stuff, Go uses an event loop like model, right?
sioa:Go has a network poller, if this is what you mean by event loop like model. I cannot tell, however, if and in which way the networking model is related to the goroutine stack size.
ChristophBerger:Yeah I just started learning about this things, so yeah I guess I am equating the network poller with the event loop.
-n-k-:To add another aspect, many real-life networking challenges are above language level, but Go can help build solutions with low effort, as this article about implementing a worker pool with goroutines and channels nicely demonstrates.
titpetric:It also matters if you're allocating heap memory. For example, if you're generating large json responses, each goroutine's json encoder will have a byte buffer, and your memory usage can get out of hand if you have a lot of goroutines.
acln0:Technically you could hit issues where one goroutine would have a high CPU use. For example, an infinite loop generating a lot of outputs that are sent into a buffered channel. The Go scheduler might decide that "hey this goroutine needs more time, let's give it to it". In practice that would mean that the secondary goroutine (reading from a channel) can be effectively stopped. The way to work around it is to call
runtime.Gosched()
from the CPU intensive routine to force the scheduler to give some time to the other goroutines.Here's a more concrete example from a SO question and answer
Edit: to add some info: the stdlib is littered with Gosched calls when it comes to IO bound workloads. People might thing that the scheduler is a thing that's running in the background, but it's closer to the main execution thread that keeps track of goroutines below it and gives time to them. Every time you call a function, or some blocking syscall gets called, the goroutine yields with a call to Gosched, so other goroutines can do work while that's waiting.
Must read: https://www.slideshare.net/matthewrdale/demystifying-the-go-scheduler, slide 13 explains exactly how netpoller works, which might be interesting as it was mentioned in the other comments.
atamiri:The Go scheduler is cooperative. Goroutines are not preempted, but they do yield the processor under certain circumstances, namely at synchronization points, function call sites and when performing I/O.
All network I/O is asynchronous under the hood and is implemented in the runtime network poller using the platform provided notification mechanism (epoll, kqueue, IOCP, etc).
Because Go uses a user space scheduler on top of the OS scheduler, you are presented with synchronous looking programming interfaces, which are easier to use and reason about than explicitly asynchronous, callback-based constructs. When a goroutine must wait for something to happen (e.g. a channel operation or I/O), it is parked, to be woken up by the scheduler when the operation is complete and it can resume execution.
/u/ChristophBerger mentions goroutine stack size, which is another key element in making the Go programming model work.
Generally, write straight-forward, synchronous code. Don't do things asynchronously just because you can (or because it is cheap to create goroutines). Introduce asynchrony only when you need it. If you're writing a network server, create O(1) goroutines per client connection, or use the ones created for you (for example: https://golang.org/src/net/http/server.go#L2720). Go allows you to write such code with the confidence that it will perform well, while hiding the hard realities of the underlying I/O under a useful abstraction.
It's blocking in the sense that a goroutine can be suspended but under the hood IO is asynchronous.
